147

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*/});
});
mandza
  • 330
  • 9
  • 24
MisterIsaak
  • 3,882
  • 6
  • 32
  • 55
  • possible duplicate of [Wait until all jQuery Ajax requests are done?](http://stackoverflow.com/q/3709597/1048572) or [jQuery Deferred - waiting for multiple AJAX requests to finish](http://stackoverflow.com/q/6538470/1048572) – Bergi Jun 25 '15 at 18:49
  • Possible duplicate of [Wait until all jQuery Ajax requests are done?](https://stackoverflow.com/questions/3709597/wait-until-all-jquery-ajax-requests-are-done) – cilf Aug 30 '17 at 06:48

14 Answers14

169

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.

starball
  • 20,030
  • 7
  • 43
  • 238
Greg
  • 6,453
  • 9
  • 45
  • 61
  • 8
    Thanks for the answer, this is definitely the way to go for me. it uses jQuery, it is compact, short and expressive. – nimcap Apr 01 '12 at 20:24
  • 3
    Subhaze's answer is great but really, this is the correct one. – Molomby Sep 06 '12 at 04:23
  • 11
    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. :( – subhaze Nov 14 '12 at 16:10
119

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);
        };
    };
})();
subhaze
  • 8,815
  • 2
  • 30
  • 33
  • 1
    Thanks for the great code example! I think I'll be able to stumble my way through the rest. Thanks again! – MisterIsaak Dec 06 '10 at 20:10
  • 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. – subhaze Dec 06 '10 at 20:19
  • 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! – MisterIsaak Dec 06 '10 at 22:47
  • 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 – subhaze Dec 06 '10 at 22:49
  • Is the `singleCallback` variable at the 2nd line unnecessary? Or am I missing something? – nimcap Apr 01 '12 at 21:11
  • 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 :) – subhaze Apr 02 '12 at 17:27
  • +1: Exactly what I was looking for (and not having to re-invent the wheel). – leppie May 30 '12 at 08:38
  • You are the man @subhaze... Great code! Glad to of found it. Props +1 – Mark Pieszak - Trilon.io Jan 31 '13 at 20:55
  • @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 :) – subhaze Jan 31 '13 at 21:20
  • I was about to make a question about this but then i found your answer, this code really helped me! thanks!! +1 – Carlos Siestrup Oct 28 '16 at 13:59
  • 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 Jul 12 '17 at 11:51
15

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]);
Cory Danielson
  • 14,314
  • 3
  • 44
  • 51
  • 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. – Greg Aug 02 '16 at 23:28
  • 3
    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. – Cory Danielson Aug 03 '16 at 16:32
  • @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? – insanely_sin Oct 14 '20 at 02:08
  • 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) => {}`. – Cory Danielson Oct 14 '20 at 16:29
15

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
        }
    });
});
Matt
  • 74,352
  • 26
  • 153
  • 180
  • 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... – Pitto Apr 28 '15 at 13:34
  • 1
    @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 (`--`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement_(--)) 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. – Matt Apr 28 '15 at 13:45
  • Simply woderful! Thanks for your time and patience. – Pitto Apr 28 '15 at 18:10
  • @Pitto: Humm.. can you link to the code you're using? It [works for me](http://jsfiddle.net/2vy60vz4/). – Matt Apr 28 '15 at 18:40
4

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

Adolph Trudeau
  • 351
  • 1
  • 8
3

EDIT -- perhaps the best option would be to create a service endpoint that does everything the three requests do. That way you only have to do one request, and all the data is where you need it to be in the response. If you find you are doing the same 3 requests over and over again, you will probably want to go this route. It is often a good design decision to set up a facade service on the server that lumps commonly used smaller server actions together. Just an idea.


one way to do it would be to create a 'sync' object in your click handler before the ajax calls. Something like

var sync = {
   count: 0
}

The sync will be bound to the scope of the success calls automatically (closure). In the success handler, you increment the count, and if it is 3 you can call the other function.

Alternatively, you could do something like

var sync = {
   success1Complete: false,
   ...
   success3Complete: false,
}

when each success is executed, it would change the value in the sync to true. You would have to check the sync to make sure that all three are true before proceeding.

Note the case where one of your xhrs does not return success -- you need to account for that.

Yet another option would be to always call the final function in your success handlers, and have it access the sync option to determine whether to actually do anything. You would need to make sure the sync is in the scope of that function though.

hvgotcodes
  • 118,147
  • 33
  • 203
  • 236
  • 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 Dec 06 '10 at 19:47
  • @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. – hvgotcodes Dec 06 '10 at 19:53
1

I found an easier way to do it without the need of extra methods that arrange a queue.

JS

$.ajax({
  type: 'POST',
  url: 'ajax1.php',
  data:{
    id: 1,
    cb:'method1'//declaration of callback method of ajax1.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method1'
  eval(cb+"(JSON.stringify(data));");//here we calling method1 and pass all data
  }
});


$.ajax({
  type: 'POST',
  url: 'ajax2.php',
  data:{
    id: 2,
    cb:'method2'//declaration of callback method of ajax2.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method2'
  eval(cb+"(JSON.stringify(data));");//here we calling method2 and pass all data
  }
});


//the callback methods
function method1(data){
//here we have our data from ajax1.php
alert("method1 called with data="+data);
//doing stuff we would only do in method1
//..
}

function method2(data){
//here we have our data from ajax2.php
alert("method2 called with data="+data);
//doing stuff we would only do in method2
//..
}

PHP (ajax1.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method1'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax1"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>

PHP (ajax2.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method2'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax2"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>
0

It's not jquery (and it appears jquery has a workable solution) but just as another option....

I've had similar problems working heavily with SharePoint web services - you often need to pull data from multiple sources to generate input for a single process.

To solve it I embedded this kind of functionality into my AJAX abstraction library. You can easily define a request which will trigger a set of handlers when complete. However each request can be defined with multiple http calls. Here's the component (and detailed documentation):

DPAJAX at DepressedPress.com

This simple example creates one request with three calls and then passes that information, in the call order, to a single handler:

    // The handler function 
function AddUp(Nums) { alert(Nums[1] + Nums[2] + Nums[3]) }; 

    // Create the pool 
myPool = DP_AJAX.createPool(); 

    // Create the request 
myRequest = DP_AJAX.createRequest(AddUp); 

    // Add the calls to the request 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [5,10]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [4,6]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [7,13]); 

    // Add the request to the pool 
myPool.addRequest(myRequest); 

Note that unlike many of the other solutions (including, I believe the "when" solution in jquery) provided this method does not force single threading of the calls being made - each will still run as quickly (or as slowly) as the environment allows but the single handler will only be called when all are complete. It also supports the setting of timeout values and retry attempts if your service is a little flakey.

I've found it insanely useful (and incredibly simple to understand from a code perspective). No more chaining, no more counting calls and saving output. Just "set it and forget it".

Jim Davis
  • 1,230
  • 6
  • 11
0

I asked the same question a while ago and got a couple of good answers here: Best way to add a 'callback' after a series of asynchronous XHR calls

Community
  • 1
  • 1
SBUJOLD
  • 1,463
  • 2
  • 11
  • 21
0

I got some good hints from the answers on this page. I adapted it a bit for my use and thought I could share.

// lets say we have 2 ajax functions that needs to be "synchronized". 
// In other words, we want to know when both are completed.
function foo1(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo1');
            callback();               
        }
    });
}

function foo2(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo2');
            callback();
        }
    });
}

// here is my simplified solution
ajaxSynchronizer = function() {
    var funcs = [];
    var funcsCompleted = 0;
    var callback;

    this.add = function(f) {
        funcs.push(f);
    }

    this.synchronizer = function() {
        funcsCompleted++;
        if (funcsCompleted == funcs.length) {
            callback.call(this);
        }
    }

    this.callWhenFinished = function(cb) {
        callback = cb;
        for (var i = 0; i < funcs.length; i++) {
            funcs[i].call(this, this.synchronizer);
        }
    }
}

// this is the function that is called when both ajax calls are completed.
afterFunction = function() {
    alert('All done!');
}

// this is how you set it up
var synchronizer = new ajaxSynchronizer();
synchronizer.add(foo1);
synchronizer.add(foo2);
synchronizer.callWhenFinished(afterFunction);

There are some limitations here, but for my case it was ok. I also found that for more advanced stuff it there is also a AOP plugin (for jQuery) that might be useful: http://code.google.com/p/jquery-aop/

PålOliver
  • 2,502
  • 1
  • 23
  • 27
0

Ok, this is old but please let me contribute my solution :)

function sync( callback ){
    syncCount--;
    if ( syncCount < 1 ) callback();
}
function allFinished(){ .............. }

window.syncCount = 2;

$.ajax({
    url: 'url',
    success: function(data) {
        sync( allFinished );
    }
});
someFunctionWithCallback( function(){ sync( allFinished ); } )

It works also with functions that have a callback. You set the syncCount and you call the function sync(...) in the callback of every action.

sic
  • 11
  • 2
0

I came across this problem today, and this was my naive attempt before watching the accepted answer.

<script>
    function main() {
        var a, b, c
        var one = function() {
            if ( a != undefined  && b != undefined && c != undefined ) {
                alert("Ok")
            } else {
                alert( "¬¬ ")
            }
        }

        fakeAjaxCall( function() {
            a = "two"
            one()
        } )
        fakeAjaxCall( function() {
            b = "three"
            one()
        } )
        fakeAjaxCall( function() {
            c = "four"
            one()
        } )
    }
    function fakeAjaxCall( a ) {
        a()
    }
    main()
</script>
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
-2
async   : false,

By default, all requests are sent asynchronously (i.e. this is set to true by default). If you need synchronous requests, set this option to false. Cross-domain requests and dataType: "jsonp" requests do not support synchronous operation. Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active. As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done() or the deprecated jqXHR.success().

mamdouh alramadan
  • 8,349
  • 6
  • 36
  • 53
-3
$.ajax({type:'POST', url:'www.naver.com', dataType:'text', async:false,
    complete:function(xhr, textStatus){},
    error:function(xhr, textStatus){},
    success:function( data ){
        $.ajax({type:'POST', 
            ....
            ....
            success:function(data){
                $.ajax({type:'POST',
                    ....
                    ....
            }
        }
    });

I'm sorry but I can't explain what I worte cuz I'm a Korean who can't speak even a word in english. but I think you can easily understand it.