jQuery callback for multiple ajax calls
Solution 1
Here is a callback object I wrote where you can either set a single callback to fire once all complete or let each have their own callback and fire them all once all complete:
NOTICE
Since jQuery 1.5+ you can use the deferred method as described in another answer:
$.when($.ajax(), [...]).then(function(results){},[...]);
for jQuery < 1.5 the following will work or if you need to have your ajax calls fired at unknown times as shown here with two buttons: fired after both buttons are clicked
[usage]
for single callback once complete: Working Example
// initialize here
var requestCallback = new MyRequestsCompleted({
numRequest: 3,
singleCallback: function(){
alert( "I'm the callback");
}
});
//usage in request
$.ajax({
url: '/echo/html/',
success: function(data) {
requestCallback.requestComplete(true);
}
});
$.ajax({
url: '/echo/html/',
success: function(data) {
requestCallback.requestComplete(true);
}
});
$.ajax({
url: '/echo/html/',
success: function(data) {
requestCallback.requestComplete(true);
}
});
each having their own callback when all complete: Working Example
//initialize
var requestCallback = new MyRequestsCompleted({
numRequest: 3
});
//usage in request
$.ajax({
url: '/echo/html/',
success: function(data) {
requestCallback.addCallbackToQueue(true, function() {
alert('Im the first callback');
});
}
});
$.ajax({
url: '/echo/html/',
success: function(data) {
requestCallback.addCallbackToQueue(true, function() {
alert('Im the second callback');
});
}
});
$.ajax({
url: '/echo/html/',
success: function(data) {
requestCallback.addCallbackToQueue(true, function() {
alert('Im the third callback');
});
}
});
[The Code]
var MyRequestsCompleted = (function() {
var numRequestToComplete, requestsCompleted, callBacks, singleCallBack;
return function(options) {
if (!options) options = {};
numRequestToComplete = options.numRequest || 0;
requestsCompleted = options.requestsCompleted || 0;
callBacks = [];
var fireCallbacks = function() {
alert("we're all complete");
for (var i = 0; i < callBacks.length; i++) callBacks[i]();
};
if (options.singleCallback) callBacks.push(options.singleCallback);
this.addCallbackToQueue = function(isComplete, callback) {
if (isComplete) requestsCompleted++;
if (callback) callBacks.push(callback);
if (requestsCompleted == numRequestToComplete) fireCallbacks();
};
this.requestComplete = function(isComplete) {
if (isComplete) requestsCompleted++;
if (requestsCompleted == numRequestToComplete) fireCallbacks();
};
this.setCallback = function(callback) {
callBacks.push(callBack);
};
};
})();
Solution 2
Looks like you've got some answers to this, however I think there is something worth mentioning here that will greatly simplify your code. jQuery introduced the $.when
in v1.5. It looks like:
$.when($.ajax(...), $.ajax(...)).then(function (resp1, resp2) {
//this callback will be fired once all ajax calls have finished.
});
Didn't see it mentioned here, hope it helps.
Solution 3
It's worth noting that since $.when
expects all of the ajax requests as sequential arguments (not an array) you'll commonly see $.when
used with .apply()
like so:
// Save all requests in an array of jqXHR objects
var requests = arrayOfThings.map(function(thing) {
return $.ajax({
method: 'GET',
url: 'thing/' + thing.id
});
});
$.when.apply(this, requests).then(function(resp1, resp2/*, ... */) {
// Each argument is an array with the following structure: [ data, statusText, jqXHR ]
var responseArgsArray = Array.prototype.slice.call(this, arguments);
});
Using the Spread syntax, you can now write this code like so:
$.when(...requests).then((...responses) => {
// do something with responses
})
This is because $.when
accepts args like this
$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);
And not like this:
$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);
Solution 4
Not seeing the need for any object malarky myself. Simple have a variable which is an integer. When you start a request, increment the number. When one completes, decrement it. When it's zero, there are no requests in progress, so you're done.
$('#button').click(function() {
var inProgress = 0;
function handleBefore() {
inProgress++;
};
function handleComplete() {
if (!--inProgress) {
// do what's in here when all requests have completed.
}
};
$.ajax({
beforeSend: handleBefore,
complete: function () {
// whatever
handleComplete();
// whatever
}
});
$.ajax({
beforeSend: handleBefore,
complete: function () {
// whatever
handleComplete();
// whatever
}
});
$.ajax({
beforeSend: handleBefore,
complete: function () {
// whatever
handleComplete();
// whatever
}
});
});
Solution 5
I like hvgotcodes' idea. My suggestion is to add a generic incrementer that compares the number complete to the number needed and then runs the final callback. This could be built into the final callback.
var sync = {
callbacksToComplete = 3,
callbacksCompleted = 0,
addCallbackInstance = function(){
this.callbacksCompleted++;
if(callbacksCompleted == callbacksToComplete) {
doFinalCallBack();
}
}
};
[Edited to reflect name updates.]
Comments
-
MisterIsaak over 3 years
I want to make three ajax calls in a click event. Each ajax call does a distinct operation and returns back data that is needed for a final callback. The calls themselves are not dependent on one another, they can all go at the same time, however I would like to have a final callback when all three are complete.
$('#button').click(function() { fun1(); fun2(); fun3(); //now do something else when the requests have done their 'success' callbacks. }); var fun1= (function() { $.ajax({/*code*/}); }); var fun2 = (function() { $.ajax({/*code*/}); }); var fun3 = (function() { $.ajax({/*code*/}); });
-
MisterIsaak over 13 yearsI like your first suggestion more for a couple reasons. Each request has a distinct purpose, so I'd like to keep them separate for that reason. Also the requests are actually in a functions because I use them elsewhere, so i was trying to keep a modular design if possible. Thanks for your help!
-
MisterIsaak over 13 years^ Agreed, helped get a better understanding of what I actually needed.
-
hvgotcodes over 13 years@Jisaak, yeah putting a method on the server to deal with this might not make sense for you. But it is acceptable to set up a facade on the server. You talk about modularity, creating one method that handles all three things is modular.
-
MisterIsaak over 13 yearsThanks for the great code example! I think I'll be able to stumble my way through the rest. Thanks again!
-
subhaze over 13 yearsNo prob, glad it helped! I got kinda carried away with it :P still have some more ideas for it. I plan to update it to where you don't have to specify a number of requests on initialization.
-
MisterIsaak over 13 yearsNice, I hope you do! Very interesting, I'd suggest throwing in the option to add an additional callback. I did and it works perfect for what I want to do. Thanks again!
-
subhaze over 13 yearsI did but forgot to add it to the usage case :P use
setCallback
to add more to the queue. Though now that I think about it... I should call itaddCallback
or something of the sort... and not just set since it's adding to the queue -
ldasilva about 12 yearsThanks for the answer, this is definitely the way to go for me. it uses jQuery, it is compact, short and expressive.
-
ldasilva about 12 yearsIs the
singleCallback
variable at the 2nd line unnecessary? Or am I missing something? -
subhaze about 12 yearsI'm not sure as to which var you're speaking of, but it's very possible as I wrote most of this out during my lunch break and meant to comb through it but got sidetracked and forgot to :)
-
leppie almost 12 years+1: Exactly what I was looking for (and not having to re-invent the wheel).
-
Molomby over 11 yearsSubhaze's answer is great but really, this is the correct one.
-
subhaze over 11 yearsI agree this is a good solution when you have jQuery 1.5+ but at the time of the answer deferred functionality was not available. :(
-
Mark Pieszak - Trilon.io over 11 yearsYou are the man @subhaze... Great code! Glad to of found it. Props +1
-
subhaze over 11 years@mcpDESIGNS thanks man! I've been aiming to clean up the code a bit more. Maybe one of these days when I find time and/or get bored :)
-
Pitto about 9 yearsThis is very simple and works perfectly... Would you mind explaining a little better what happens in if (--inProgress)? It should control if variable inProgress == 0 and subtract one unit to it but I don't get the compact syntax...
-
Matt about 9 years@Pitto: Oops... actually, there's a logic error there, and it should be
if (!--inProgress)
. The correct version is analogous toinProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }
. The compact form combines the prefix decrement operator (--
) and the negation operator (!
) to evaluate to "true" (and therefore execute theif
block), if decrementinginProgress
results ininProgress == 0
, and evaluates to "false" (and therefore does nothing) otherwise. -
Pitto about 9 yearsSimply woderful! Thanks for your time and patience.
-
Matt about 9 years@Pitto: Humm.. can you link to the code you're using? It works for me.
-
user5670895 over 8 yearsThis is not an adequate solution. You should never make a synchronous Ajax request.
-
Greg almost 8 yearsGreat response. There are a lot of good promise libs now, most of those have an
all
method or something similar, but I like the map concept. Nice. -
Cory Danielson almost 8 yearsYeah I almost always use $.when by applying an array of requests onto it, and didn't spot that in a solution here, so just added it to have a good example here.
-
Carlos Siestrup over 7 yearsI was about to make a question about this but then i found your answer, this code really helped me! thanks!! +1
-
traeper almost 7 yearsThanks for EXCELLENT example!! But numRequestToComplete, requestsCompleted are actually global variables. So if you create more 2 instances, result in sharing these variables. Be careful to use~
-
traeper almost 7 yearsShould not use ajax this way. Callback Hell..!
-
insanely_sin over 3 years@CoryDanielson My resp1, resp2 are variable numbers depending on the size of the array passed in apply(). Is there a way I can make it work?
-
Cory Danielson over 3 yearsYes - you can refer to the array of function arguments with the
arguments
variable. If you're using newer JS features you can spread them into a variable too(...args) => {}
.