Coordinating multiple ajax requests with jquery.when


While building a rich JavaScript application, you may get in a situation where you need to make multiple ajax requests, and it doesn’t make sense to work with the results until after all of them have returned. For example, suppose you wanted to collect the tweets from three different users, and display the entire set sorted alphabetically (yes, its contrived). To get the tweets for a user via jQuery, you write code like:

$.get("http://twitter.com/status/user_timeline/SCREEN_NAME.json",
  function(tweets){
    // work with tweets
   },
  "jsonp");

(For the purposes of this example, I’m going to assume there is no way to get the tweets for multiple users in a single call)

To get the tweets for three users, you would need to make three separate $.get calls to the user_timeline endpoint. Since each call is executed asynchronously, with no guarantee which would return first, the code to coordinate the response for all three users would likely be a mess of shared state and/or nested callbacks.

As of jQuery 1.5, the solution is much simpler. Each of the ajax functions were changed to return a Deferred object which manages the callbacks for a call. (The Deferred object is beyond the scope of this post, but I encourage you to read the documentation for a more thorough explanation.) The power of Deferred objects become apparent when used with the new jquery.when utility function. jquery.when accepts any number of Deferred objects, and allows you to assign callbacks that will be invoked when all of the Deferred objects have completed. The Deferred objects returned by the ajax functions are complete when the responses are received. This may sound confusing, but it should be much clearer when you see it applied to the example scenario:

  • I have a helper method, getTweets, which returns the return value of a call to $.get. This will be a Deferred object representing that call to the twitter server.
    • I call $.when, passing it the three Deferred objects from the three ajax calls.
      • The done() function is chained off of $.when to declare the code to run when all three ajax calls have completed successfully.
        • The done() function receives an argument for each of the ajax calls. Each argument holds an array of the arguments that would be passed to that ajax call’s success callback. The $.get success callback gets three arguments: data, textStatus, and jqXHR. Therefore, the data argument from the call for @greenling_com tweets is available in greenlingArgs[0]. Similarly, the textStatus argument for the call for @austintexasgov tweets would be in atxArgs[1].
          • The fifth line creates the allTweets array combining the tweets (the first, or data, argument) from all three calls to twitter.</ul> It it that last point that is interesting to me. I’m able to work with a single collection containing data from three separate ajax requests, without writing any awkward synchronization code.

          Play with the example on jsFiddle

Run QUnit tests under Continuous Integration with NQUnit
Great time to be a developer

Comments

Commenting is closed.

Zias 2099 Oct 24, 2011

I thought it was When, Then, and Done

Andy Cohen Dec 18, 2011

very useful.  i didnt realize the arguments passed into the done function provided access to the success callback args.  question; how do you get access to the complete, error, or other ajax callback arguments?

newbreedofgeek Mar 1, 2012

Thanks very much for this.. I have been looking for this for a long time. Your example makes it very easy to understand the complicated deferred object articles on the net. Good work.

newbreedofgeek Mar 1, 2012

hi there,

Just a question on this:

I'm trying to send a collection (array) of deferred objects to .when like this:

var deferredCollection = [];
deferredCollection.push(getTweets('austintexasgov'));
deferredCollection.push(getTweets('greenling_com'));
deferredCollection.push(getTweets('themomandpops'));
deferredCollection.push(getTweets('anotherUserA'));
deferredCollection.push(getTweets('anotherUserB'));

$.when( deferredCollection).done(function(atxArgs, greenlingArgs, momandpopsArgs, anotherUserAArgs, anotherUserBArgs){   
// there is an error here trying to access atxArgs etc   
  });

is it possible to do something like this??

Thanks in advance.

joshuaflanaganauthor Mar 1, 2012

I think you would have to do something like:

$.when.apply(this, deferredCollection).done...  (etc)

newbreedofgeek Mar 1, 2012

 That's smart and it worked... thanks!

kolito007 Feb 15, 2015

and what about the results?

HB Apr 18, 2012

Excellent writeup. Thanks a lot. Your explanation clarified this in a way that the docs couldn't. Thanks again!

David Feinberg Aug 28, 2012

with when if one request fails then they all fail. how can you wait for everything to execute even failures?

Kalina Todorova Feb 22, 2013

line 9 should not be really there :) I suppose that is cp fail :)

joshuaflanaganauthor Feb 22, 2013

Good catch - fixed! As you can see in the jsfiddle, that line closed the jQuery onReady callback, which wasn't included in this snippet.

Oisin Conolly Oct 13, 2013

I wish I had known about this long ago...this will make my life so much easier, and my coding much cleaner and better optimised. Thanks!

shital Aug 25, 2016

how to show ajax loader ..with this example