Tracking/ joining parallel AJAX requests with jQuery

When launching a number of AJAX requests simultaneously, it can often be painful to track the requests, and seemingly necessitates the introduction of counter variables to detect when all the AJAX requests have completed. However, since jQuery 1.5, the various AJAX methods have returned a Promise object (an object which exposes only the “safe” methods of a Deferred). This, coupled with the jQuery.when method, allows you to easily add handlers to handle either the successful completion of all AJAX requests, or the failure of one; without the need for you to track anything yourself! To use this approach, simply pass the Promise’s returned by your AJAX calls as arguments to jQuery.when(), then add handlers using the done(), fail() and always() to the Promise returned by jQuery.when(), as demonstrated in the example below.

jQuery.when(jQuery.get('/foo'), jQuery.post('/bar'), jQuery.get('/baz')).done(function (a, b, c) {
    $('#output_a').text(a[0]);
    $('#output_b').text(b[0]);
    $('#output_c').text(c[0]);
});

The ith argument passed to done() is an array of arguments that correspond to the response of the ith Promise passed to when() (i.e. the arguments are available in the order they were provided; the order the responses were received in make no difference). For the example above, this means that a, b and c are all arrays of [ data, textStatus, jqXhr ]. To get the textStatus of the second AJAX call we’d use b[1]. To get the jqXhr object of the 3rd AJAX call we’d use c[2]. All fail() handlers are invoked the first time one of the provided Promises fails. Here however, the arguments are provided as they would be passed into a fail() handler bound directly to the failed AJAX request; i.e. the first argument is the jqXHR object, the second is the textStatus and the third is the error thrown. The behaviour of always() changes depending on whether a request failed or not; if all requests succeed, always() behaves like done(). If a request fails, always() behaves like fail().

jQuery.when(jQuery.get('/foo'), jQuery.post('/bar'), jQuery.get('/baz')).done(function (a, b, c) {
    $('#output_a').text(a[0]);
    $('#output_b').text(b[0]);
    $('#output_c').text(c[0]);
}).fail(function (jqXhr, textStatus, error) {
    alert('A request failed with the error "' + jqXhr.status + '"');
}).always(function () {
    // because of the varying parameter behaviour, its difficult to
    // do anything which uses them.

    $('#loading_spinner ').hide();
});

The signature of jQuery.when() (e.g. having to pass each Promise as a separate argument) can be cumbersome, and make it seem impossible to use when the number of AJAX requests you have cannot be determined. However, we can easily use Function.apply(), and use jQuery.when() using an array of Promise‘s instead;

var ajaxRequests = [jQuery.get('/foo'), jQuery.post('/bar'), jQuery.get('/baz')];

jQuery.when.apply(jQuery, ajaxRequests).done(function () {
    for (var i=0;i<arguments.length;i++) {
        console.log('Response for request #' + (i + 1) + ' is ' + arguments[i][0]);
    }
});

If you have not come across the arguments object yet, you can read about it here. As can be seen in the example above, apply() returns the “master-Deferred” the same way using jQuery.when() directly does, allowing you to bind done(), fail() and always() handlers as before.


For more reading, check out the jQuery documentation for jQuery.when(), or the question on Stack Overflow that triggered me to write this blog post!

2 thoughts on “Tracking/ joining parallel AJAX requests with jQuery

  1. This was easier to understand for my use-case (an arbitrary number of ajax-requests) than the actual (good) jQuery documentation. Thanks!

    P.S. — The middle code example above has an error: the second ‘.done()’ should be a ‘.fail()’.

  2. Hi Rand. Glad to hear the article helped you out ;). Thanks for keeping me on-my-toes with the typeo as well… I’ve updated it accordingly :).

    Cheers,
    Matt

Leave a Reply

Your email address will not be published. Required fields are marked *