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:

$.when( getTweets('austintexasgov'),
        getTweets('greenling_com'),
        getTweets('themomandpops')
      ).done(function(atxArgs, greenlingArgs, momandpopsArgs){
    var allTweets = [].concat(atxArgs[0]).concat(greenlingArgs[0]).concat(momandpopsArgs[0]);
    var sortedTweets = sortTweets(allTweets);
    showTweets(sortedTweets);
      });

var getTweets = function(user){
    var url='http://twitter.com/status/user_timeline/' + user + '.json';
    return $.get(url, {count:5}, null, 'jsonp');
}

// see it in action at http://jsfiddle.net/94PGy/4/

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

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

Related Articles:

This entry was posted in jquery. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Bot2099

    I thought it was When, Then, and Done

  • Andrew Cohen

    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?

  • http://newbreedofgeek.blogspot.com newbreedofgeek

    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.

  • http://newbreedofgeek.blogspot.com newbreedofgeek

    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.

    • Anonymous

      I think you would have to do something like:

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

      • http://newbreedofgeek.blogspot.com newbreedofgeek

         That’s smart and it worked… thanks!

  • HB

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

  • http://rawberg.com/blog David Feinberg

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

  • Kalina Todorova

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

    • joshuaflanagan

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

  • http://oacdesigns.com/ Oisin Conolly

    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!