jQuery callback for multiple ajax calls

180,313

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){},[...]);

Example of deferred here

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.]

Share:
180,313
MisterIsaak
Author by

MisterIsaak

Lower level rules lead to high level behaviors

Updated on October 15, 2020

Comments

  • MisterIsaak
    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
    MisterIsaak over 13 years
    I 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
    MisterIsaak over 13 years
    ^ Agreed, helped get a better understanding of what I actually needed.
  • hvgotcodes
    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
    MisterIsaak over 13 years
    Thanks for the great code example! I think I'll be able to stumble my way through the rest. Thanks again!
  • subhaze
    subhaze over 13 years
    No 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
    MisterIsaak over 13 years
    Nice, 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
    subhaze over 13 years
    I 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 it addCallback or something of the sort... and not just set since it's adding to the queue
  • ldasilva
    ldasilva about 12 years
    Thanks for the answer, this is definitely the way to go for me. it uses jQuery, it is compact, short and expressive.
  • ldasilva
    ldasilva about 12 years
    Is the singleCallback variable at the 2nd line unnecessary? Or am I missing something?
  • subhaze
    subhaze about 12 years
    I'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
    leppie almost 12 years
    +1: Exactly what I was looking for (and not having to re-invent the wheel).
  • Molomby
    Molomby over 11 years
    Subhaze's answer is great but really, this is the correct one.
  • subhaze
    subhaze over 11 years
    I 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
    Mark Pieszak - Trilon.io over 11 years
    You are the man @subhaze... Great code! Glad to of found it. Props +1
  • subhaze
    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
    Pitto about 9 years
    This 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
    Matt about 9 years
    @Pitto: Oops... actually, there's a logic error there, and it should be if (!--inProgress). The correct version is analogous to inProgress = 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 the if block), if decrementing inProgress results in inProgress == 0, and evaluates to "false" (and therefore does nothing) otherwise.
  • Pitto
    Pitto about 9 years
    Simply woderful! Thanks for your time and patience.
  • Matt
    Matt about 9 years
    @Pitto: Humm.. can you link to the code you're using? It works for me.
  • user5670895
    user5670895 over 8 years
    This is not an adequate solution. You should never make a synchronous Ajax request.
  • Greg
    Greg almost 8 years
    Great 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
    Cory Danielson almost 8 years
    Yeah 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
    Carlos Siestrup over 7 years
    I was about to make a question about this but then i found your answer, this code really helped me! thanks!! +1
  • traeper
    traeper almost 7 years
    Thanks 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
    traeper almost 7 years
    Should not use ajax this way. Callback Hell..!
  • insanely_sin
    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
    Cory Danielson over 3 years
    Yes - 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) => {}.