Chain multiple POST requests together using AJAX and jQuery - ajax

I received a suggestion from a prior question that I need to amend my code chain a series of POST requests together, but I don't have any idea how to accomplish this. Specifically, the advice I was given was to:
fire off a post, have its success handler fire off the next post,
etc... and then when all the posts are done, the final post's success
handler fires off the get
This strategy makes sense to me but I do not know how to implement. I am trying to prevent the call to GET before all of the calls to POST have completed. Currently, I have implemented $.when.apply to delay the sending of GET. Here is the code for that:
function(){
$.when.apply(undefined, InsertTheAPPs()).done(function () {
$.ajax({
url: sURL + "fileappeal/send_apps_email",
success: function() {
var m = $.msg("my message",
{header:'my header', live:10000});
setTimeout(function(){
if(m)m.setBody('...my other message.');
},3000);
setTimeout(function(){
if(m)m.close(function(){
window.location.replace(sURL+'client/view');
});
},6000);
$('#ajaxShield').fadeOut(1000);},
error: function(){
$.msg("error message",
{header:'error header', live:10000});
}
});
});
}
Here is the code for the jQuery $.each loop. This is the code that needs to not only begin, but must end before the ajax call to fileappeal/send_apps_email above:
function InsertTheAPPs(){
$('input[name=c_maybe].c_box').each(function(){
var jqxhrs = [];
if($(this).prop('checked')){
var rn = $(this).prop('value');
jqxhrs.push(
$.ajax({
url: sURL + 'fileappeal/insert_app',
type:"POST",
dataType: 'text',
data: {'rn': rn},
error: function(data) {console.log('Error:'+rn+'_'+data);}
})
)
return jqxhrs;
}
});
}
Could someone demonstrate how I can modify the code above to implement the strategy of chaining together the multiple POST calls?

Don't return from .each. It doesn't work that way. Instead do this:
var jqxhrs = [];
$(...).each(...
});
return jqxhrs;
Nothing is assigned to the return value of .each, which you can't get anyway. Returning from each allows it to be used like break/continue, which doesn't make sense in your context.
Moreover, the var jqxhrs inside of the each loop causes a new variable to be declared in that context on each iteration of the loop.

Related

Observable values disappearing after being pushed into observableArray

I'm grabbing data from the server and pushing them into an observable array.
I'm pushing observables into an observable array.
As I push the data into the observables, the observables contain the data.
However as soon as I push the observables into the observable Array, a few of the observables are missing data.
self.mealFoods([]);
$.ajax({
url: "/mealsurl/1",
async: false,
dataType: 'json',
success: function(datad) {
for(var lia = 0; lia < datad.length; lia++){
var cats_url = "/catsurl/" + datad[lia].category_id;
var units_by_food_url = "/unitsurl/" + datad[lia].ndb_no;
var foodThing = new NewFood();
foodThing.foodId(parseInt(datad[lia].id)); //works
foodThing.category(parseInt(datad[lia].category_id)); //works
$.ajax({
url: cats_url,
dataType: 'json',
success: function(dat) {
foodThing.category_foods(dat); //works
}
});
foodThing.food(datad[lia].ndb_no); //works
$.ajax({
url: units_by_food_url,
dataType: 'json',
success: function(dat) {
foodThing.food.units(dat); //works
}
});
foodThing.unit(parseInt(datad[lia].seq)); //works
foodThing.number_of_unit(datad[lia].this_much); //works
self.mealFoods.push(foodThing);
// At this point when looking inside the mealFoods array: self.mealFoods()[0].food(), self.mealFoods()[0].unit(), self.mealFoods()[0].food.units(), self.mealFoods()[0].category_Foods() ALL ARE EMPTY
}
}
});
You, sir, are having a classic case of async-brain-melt. It is a common sympton in beginners but never fear for the recovery rate is nearly 100%. :)
I would wager your experience is with synchronous languages, that is, where if one line is written after the other, the lines written before are executed before, always.
A normal JavaScript function is synchronous. For example:
console.log(1);
console.log(2);
As expected, this prints 1 and then 2.
However, asynchronous code is not necessarily executed in the order it was declared. Consider this example using a setTimeout function, which schedules a function for later execution:
setTimeout(function(){ console.log(1); }, 1000);
console.log(2);
Now, the output will be 2 and 1, because 1 only ran 1000 millis after the setTimeout call.
So, I imagine you are beginning to understand how this applies to your problem.
Your calls to cats_url and units_by_food_url are asynchronous. Therefore, the following code does not wait for them to finish. So, when you access self.mealFoods()[0].food.units(), the success function has not yet grabbed the data!
What you need to do is to coordinate your asynchronous calls appropriately. There are many ways to achieve that. First, I'll teach you the most simple strategy, using only functions:
Grab the list from the server
When you have the list, iterate over each meal and start two ajax calls (up to here, you are already doing everything right)
Now comes the magic: when you have the results for either ajax call, you call an "itemComplete" function. This function will sync the two calls - it will only proceed if the two calls finished.
Finally, call a "listComplete" function each time any item is complete. This function must also check if all items are complete before proceeding.
So, it would look something like this:
$.ajax({
url: "/meals/1",
dataType: 'json',
success: function(list) {
var observableArray = ko.observableArray([]); // this will hold your list
var length = list.length;
var tries = 0;
var listComplete = function () {
tries++;
if (tries == length) {
// Hooray!
// All your items are complete.
console.log(observableArray());
}
};
list.forEach(function(item){
var propertyOneUrl = item.propertyOneUrl;
var propertyTwoUrl = item.propertyTwoUrl;
var propertyOneComplete = false;
var propertyTwoComplete = false;
var food = new Food(item.id);
var itemComplete = function () {
if (propertyOneComplete && propertyTwoComplete) {
// This item is complete.
observableArray.push(food);
// Let's warn list complete so it can count us in.
listComplete();
}
};
// Start your ajax calls
$.ajax({
url: propertyOneUrl,
dataType: 'json',
success: function (propertyOne) {
food.propertyOne(propertyOne);
// Declare that your first property is ready
propertyOneComplete = true;
// We can't know which property finishes first, so we must call this in both
itemComplete();
}
});
$.ajax({
url: propertyTwoUrl,
dataType: 'json',
success: function (propertyTwo) {
food.propertyTwo(propertyTwo);
// Declare that your second property is ready
propertyTwoComplete = true;
// We can't know which property finishes first, so we must call this in both
itemComplete();
}
});
}); //for each
} // success
});
Now, you probably realize how tiresome that pattern can be. That's why there are other ways to better solve this problem. One of these is a pattern called "Promises". You can learn more about them in these links:
https://www.promisejs.org/
http://blog.gadr.me/promises-are-not-optional/
And you'll be happy to know that jQuery.ajax() returns a Promise! So, now you can try and solve that problem using Promises. You'll end up with a much cleaner code.
Hope you make it!
It's because you are doing async ajax calls in a loop. Because whenever an ajax call is made it the loop continues it means that by the time the response comes back the object assigned to foodThing is now no longer what it was set to before the ajax call. Because a for loop is so quick is most likely that only the last object created in the loop is updated.
If you have a look at this simple loop it has the same problem:
for (var i = 0; i < 10; i++){
var a = new NewFood(i);
$.ajax({
url: "/catsurl/1",
dataType: 'json',
success: function(dat) {
console.debug(a.id);
}
});
}
By the time the ajax call comes back a has changed and what ends up happening is only 9 gets written out 10 times: http://jsfiddle.net/r6rwbtb9/
To fix this we would use a closure which is essentially wrapping the ajax call in a function in which we self contain the item we want to do something with:
for (var i = 0; i < 10; i++){
var a = new NewFood(i);
(function (a) {
$.ajax({
url: "/catsurl/1",
dataType: 'json',
success: function(dat) {
console.debug(a.id);
}
});
})(a);
}
And then you can see that the numbers 0-9 are output to the console: http://jsfiddle.net/r6rwbtb9/1/. It's also interesting to note that you can't ensure that each request will necessarily come back in the the same order. That is why sometimes the numbers could come back in a different order to 0-9 because some requests are quicker than others.
SO back to your code. In order to make sure you are updating the correct item for each callback you need to use a closure for each ajax call. There was also a problem with foodThing.food.units(dat) which needed to be foodThing.food().units(dat) as foodThing.food() is an observable.
So to wrap in closures we need to change the two ajax calls to this:
(function(category_foods){
$.ajax({
url: cats_url,
dataType: 'json',
success: function(dat) {
category_foods(dat);
}
});
})(foodThing.category_foods);
(function(units){
$.ajax({
url: units_by_food_url,
dataType: 'json',
success: function(dat) {
units(dat);
}
});
})(foodThing.food().units);

Delay GET until all POST methods have completed, not just begun

I am experiencing two issues with my jQuery record-inserting process, and I am hoping that this wonderful SO community can help me to solve at least one of those issues. These are the issues:
Issue 1 - Intermittent server delay
The first issue relates to the fact that my Ubuntu 10.04 server seems to exhibit intermittent, 4.5 second delays when doing a POST of data to the mySQL database. Most POST commands are executed within a normal amount of milliseconds, but when a delay occurs, it always seems to be for approximately 4.5 seconds. This is not a busy, public server so it shouldn't be a matter of server load being the problem. These short videos demonstrate what I am trying to explain:
Video 1
Video 2
I have posted a question on serverfault and am awaiting some input from that forum which is probably more appropriate for this Issue 1.
Issue 2 - Timing of jQuery POST and GET Methods
The real issue that I am trying to resolve is to prevent the call to GET before all of the calls to POST have completed. Currently, I have implemented $.when.apply to delay the sending of GET. Here is the code for that:
function(){
$.when.apply(undefined, InsertTheAPPs()).done(function (){
$.ajax({
url: sURL + "fileappeal/send_apps_email",
success: function() {
var m = $.msg("my message",
{header:'my header', live:10000});
setTimeout(function(){
if(m)m.setBody('...my other message.');
},3000);
setTimeout(function(){
if(m)m.close(function(){
window.location.replace(sURL+'client/view');
});
},6000);
$('#ajaxShield').fadeOut(1000);
},
error: function(){
$.msg("error message",
{header:'error header', live:10000});
}
});
});
}
My problem arises due to the delay described above in Issue 1. The GET method is being called after all of the POST methods have begun, but I need the GET method to wait until all of the POST methods have ended. This is the issue that I need assistance with. Basically, what is happening is happening wrong here is that my confirmation email is being sent before all of the records have been completely inserted into the mySQL database.
Here is the code for the jQuery $.each loop. This is the code that needs to not only begin, but must end before the ajax call to fileappeal/send_apps_email above:
function InsertTheAPPs(){
$('input[name=c_maybe].c_box').each(function(){
var jqxhrs = [];
if($(this).prop('checked')){
var rn = $(this).prop('value');
jqxhrs.push(
$.ajax({
url: sURL + 'fileappeal/insert_app',
type:"POST",
dataType: 'text',
data: {'rn': rn},
error: function(data) {console.log('Error:'+rn+'_'+data);}
})
)
return jqxhrs;
}
});
}
Anyone have any suggestions for how I can workaround the server delay issue and prevent the call to the GET before all of the POST methods have completed? Thanks.
There's a small problem with your post. After you resolve it, this post should help you finish out your code: jQuery Deferred - waiting for multiple AJAX requests to finish
You're returning inside the .each but the function itself doesn't return anything. So your delay is not being given the array of ajax calls to wait for. And also, since your jqhrs is defined inside the each, the scope is per iteration over each c_box. Your method should look like this:
function InsertTheAPPs(){
var jqxhrs = [];
$('input[name=c_maybe].c_box').each(function(){
if($(this).prop('checked')){
var rn = $(this).prop('value');
jqxhrs.push(
$.ajax({
url: sURL + 'fileappeal/insert_app',
type:"POST",
dataType: 'text',
data: {'rn': rn},
error: function(data) {console.log('Error:'+rn+'_'+data);}
})
)
}
});
return jqxhrs;
}
You can also make your code easier. Since you just want to know if something is checked you can use the jquery pseudo class filter :checked such as:
function InsertTheAPPs(){
var jqxhrs = [];
$('input[name=c_maybe].c_box').filter(':checked').each(function(){
var rn = $(this).prop('value');
jqxhrs.push(
$.ajax({
url: sURL + 'fileappeal/insert_app',
type:"POST",
dataType: 'text',
data: {'rn': rn},
error: function(data) {console.log('Error:'+rn+'_'+data);}
})
)
});
return jqxhrs;
}
You could combine the filter on :checked into the main filter such as $('input[name=c_maybe].c_box:checked') but I left it in long form to really demonstrate what was going on.

Javascript Mootools event stop

well,i have this script that i have set up that on submit requests php file that is used to validate my input values...i have set up that when it is not validated the correct way to echo it is not validated,now i want in my request when i get response and it is the error response to stop the submit..here is the script,it does send the request and returns response but it doesnt stop the submit...
i have made it like this but now i need to pop a confirm message when it is all done,i want to stop the form from executing when message pops up and if the user clicks yes to continue with the form...i tried it like this with fireEvent but it wont work...help guys!
window.addEvent('domready', function(){
var form=document.adminForm;
form.addEvent('submit', function(e){
var success = false;
var dataString="date=" + document.getElementById("date").value + "&start=" + document.getElementById("start").value + "&end=" + document.getElementById("end").value;
var requestData = new Request ({
url: '/provjera.php',
method:'POST',
async:false,
data:dataString,
onComplete: function(responseText){
var requestData2 = new Request({
url:'/posalji.php',
method:'POST',
data:dataString,
onComplete:function(responseText){
}
});
requestData2.send();
success= responseText == 'NO ERROR';
if(responseText == 'NO ERROR'){
}else{
alert("FAIL");
}
}
});
requestData.send();
if(success){
var result=confirm("Are you sure!?");
e.stop();
if(result){
form.fireEvent("submit");
}
}else{
e.stop();
}
});
});
This won't work, it breaks the asynchronous nature of XHR (it's *A*JAX, heh).
The way it works is this:
[form]
[submit event]
\->[function]->[xhr]->[onComplete function much later]->[e.stop() not applicable]
\->[function continues and terminates]
By the time the onComplete arrives and calls .stop(), the parent function execution has exited and failed to stop the event, it has already bubbled... XHR is non-blocking!
you have 2 patterns you can do to work around that:
1. always stop event, do something extra in onComplete
essentially, whatever the XHR passes to your onComplete can let you determine the success/failure of your operation and you can call another function, fire an event or do what you need to do (eg, transition page or display validation errors on screen or whtever).
2. use sync AJAX (anti-pattern)
you can actually make your XHR blocking if you wanted to so that in this execution context you can set a variable or stop the event from the onComplete - do so by passing async: false to your Request constructor options.
I would definitely not recommend 2 unless you are doing something like username availability checker onBlur/onChange that needs to block the thread before they submit. And even then, you can do it gracefully w/o this.
edit as per request, here is an example: http://jsfiddle.net/dimitar/du5s4/
var form = document.id('adminForm');
form.addEvent('submit', function (e) {
var success = false;
// simulate a server response of two types.
this.getElement('input[name=html]').set('value', ['success','error'].getRandom());
var requestData = new Request({
url: '/echo/html/',
method: 'post',
async: false,
data: this,
onComplete: function (responseText) {
// if server returned success, don't stop the event.
success = this.response.text == 'success';
console.log(success);
}
}).send();
success || e.stop();
});
this has been tailored for the jsfiddle api for ajax testing but you get the idea. since you evalResponse, your response can also set variables - though I don't remember what the scope of evluation will be - it may be the global object and not the inner scope of the submit function.
once again, this is totally wrong, use sparringly. you need to change over to a proper event based setup.
http://jsfiddle.net/dimitar/du5s4/2/ - same thing but w/o the async hack.
var form = document.id('adminForm');
form.addEvent('submit', function (e) {
e && e.stop && e.stop();
var self = this;
// simulate a server response of two types.
this.getElement('input[name=html]').set('value', ['success','error'].getRandom());
var requestData = new Request({
url: '/echo/html/',
method: 'post',
data: this,
onComplete: function (responseText) {
// if server returned success,
// call something like form.submit();
this.response.text == 'success' && self.submit();
console.log(this.response.text);
}
}).send();
});

jQuery - AJAX request using both native XHR and flXHR (Flash) fallback - how to minimize code duplication?

I need to retrieve data via cross-domain XMLHttpRequest. To make this work in (almost) all browsers, I use native XHR first and, if that fails, flXHR.
The (working) code I currently have for this is as follows:
jQuery.support.cors = true; // must set this for IE to work
$.ajax({
url: 'http://site.com/dataToGet',
transport : 'xhr',
success: function(data) {
console.log('Got data via XHR');
doStuff(data);
},
error: function(xhr, textStatus, error) {
console.log('Error in xhr:', error.message);
console.log('Trying flXHR...');
$.ajax({
url: 'http://site.com/dataToGet',
transport : 'flXHRproxy',
success: function (data) {
console.log('Got data via flXHR');
doStuff(data);
},
error: function (xhr, textStatus, error) {
console.log('Error in flXHR:', error.message);
console.log('Both methods failed, data not retrieved.');
}
});
}
});
This feels like a lot of code duplication to me, especially in the success handlers. Is there a more efficient way to do this? I'd really prefer to make one $.ajax call that would try both transports in turn, instead of having to use the error handler to make the call a second time. It's not too bad in this example, but rapidly gets more complicated if the success handler is longer or if the success handler has to itself issue another $.ajax call.
I've created a jquery-specific and slimmed-down fork of flxhr that simplifies your code sample above. You can see an example of usage in the "Usage" section in the README.
https://github.com/b9chris/flxhr-jquery-packed
In particular, you don't want to waste time waiting for a standard CORS request to fail. It's easy to determine whether flxhr is necessary by testing $.support.cors upfront (no need to override it). Then just use flxhr explicitly where necessary.
Why don't you just wrap this in a function by itself? That's after all, how you end up reusing code. You can even pass functions as arguments to make sure that you don't have to repeat this code more than once.
To me this is pretty straight forward but maybe I've misunderstood.
function xhr(success) {
$.ajax({
success: success,
error: function() {
$.ajax({ success: success })
}
});
}
Then just pass the success handler once
xhr(function(data){/*magic*/});
Or if you wanna basically avoid redundant configuration of the ajax call use the first object as a template, like this:
function xhr(success) {
var ajaxParams = { success: success };
ajaxParams.error = function() {
$.ajax($.extend(ajaxParams, { transport: 'xhr' }));
}
$.ajax(ajaxParams);
}
I simplified the whole thing a bit, but I hope you get the point.
Edit
Reading that last bit, maybe this will give you some ideas... it's a variation of that last snippet.
function xhr(success) {
var ajaxParams = { success: success };
ajaxParams.error = function() {
var newParams = $.extend(ajaxParams, { transport: 'xhr' });
newParams.success = function() {
// do something
// arguments is a special array, even if no parameters were
// defined in any arguments where passed they will be found
// in the order they were passed in the arguments array
// this makes it possible to forward the call to another
// function
success.apply(this, arguments);
}
$.ajax(newParams);
}
$.ajax(ajaxParams);
}

jQuery.ajax() sequential calls

Hey. I need some help with jQuery Ajax calls. In javascript I have to generste ajax calls to the controller, which retrieves a value from the model. I am then checking the value that is returned and making further ajax calls if necessary, say if the value reaches a particular threshold I can stop the ajax calls.
This requires ajax calls that need to be processes one after the other. I tried using async:false, but it freezes up the browser and any jQuery changes i make at the frontend are not reflected. Is there any way around this??
Thanks in advance.
You should make the next ajax call after the first one has finished like this for example:
function getResult(value) {
$.ajax({
url: 'server/url',
data: { value: value },
success: function(data) {
getResult(data.newValue);
}
});
}
I used array of steps and callback function to continue executing where async started. Works perfect for me.
var tasks = [];
for(i=0;i<20;i++){
tasks.push(i); //can be replaced with list of steps, url and so on
}
var current = 0;
function doAjax(callback) {
//check to make sure there are more requests to make
if (current < tasks.length -1 ) {
var uploadURL ="http://localhost/someSequentialToDo";
//and
var myData = tasks[current];
current++;
//make the AJAX request with the given data
$.ajax({
type: 'GET',
url : uploadURL,
data: {index: current},
dataType : 'json',
success : function (serverResponse) {
doAjax(callback);
}
});
}
else
{
callback();
console.log("this is end");
}
}
function sth(){
var datum = Date();
doAjax( function(){
console.log(datum); //displays time when ajax started
console.log(Date()); //when ajax finished
});
}
console.log("start");
sth();
In the success callback function, just make another $.ajax request if necessary. (Setting async: false causes the browser to run the request as the same thread as everything else; that's why it freezes up.)
Use a callback function, there are two: success and error.
From the jQuery ajax page:
$.ajax({
url: "test.html",
context: document.body,
success: function(){
// Do processing, call function for next ajax
}
});
A (very) simplified example:
function doAjax() {
// get url and parameters
var myurl = /* somethingsomething */;
$.ajax({
url: myurl,
context: document.body,
success: function(data){
if(data < threshold) {
doAjax();
}
}
});
}
Try using $.when() (available since 1.5) you can have a single callback that triggers once all calls are made, its cleaner and much more elegant. It ends up looking something like this:
$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
// a1 and a2 are arguments resolved for the page1 and page2 ajax requests, respectively
var jqXHR = a1[2]; /* arguments are [ "success", statusText, jqXHR ] */
alert( jqXHR.responseText )
});

Resources