jQuery: Multiple AJAX and JSON Requests, One Callback
I've been working on a new feature for the Mozilla Developer Network which requires loading of a basic script file as well as a JSON stream. Since we use jQuery, that means a jQuery.getScript
and a jQuery.getJSON
. I know those both work asynchronously and return a Deferred, so I wondered if there was a way that I could load them in parallel with one callback, much the way JavaScript loaders like curljs do. I was in luck! Using jQuery.when
, I can load both requests concurrently with one callback!
The jQuery JavaScript
As I mentioned, my use case was loading a script and a JSON resource, so how it works is:
$.when( $.getScript('/media/js/wiki-min.js?build=21eb633'), $.getJSON('https://developer.mozilla.org/en-US/demos/feeds/json/featured/') ).then(function(a, b) { // or ".done" // Yay, stuff loaded and now we can do something! });
When the resources are done loading, the done
or then
callback fires and I know the requests are complete. Each request type provides a different callback argument object, so the above would provide:
// [response, state, jqxhr], [response, state, jqxhr] ["(function(c){var e=c(".from-search-navigate");if(e…;if(j){g.apply(m,l)}}}})(window,document,jQuery);", "success", Object] [Array[15], "success", Object]
If we wanted to add a traditional AJAX XHR request to the mix, say a widget template, we could do that too:
$.when( $.getScript('/media/js/wiki-min.js?build=21eb633'), $.getJSON('https://developer.mozilla.org/en-US/demos/feeds/json/featured/'), $.get('/') ).then(function(a, b, c) { console.log(a, b, c); });
The Dojo Toolkit has had this type of functionality for a long time but I'm stoked that modern jQuery allows for the same. Making multiple requests with one callback seems as relevant as any other task these days, so jQuery's definitely moving with the times!
You can also use .then to normalize or even combine the responses, parsing the arguments (rather than explicitly assigning them in
.then(function)
as this gets a little tricky with.when
).Since when accepts an array of Deffereds, you can set it up so that it can handle an array of any length, combining the results all into one response before success/fail handlers are fired. That way your success/fail handlers don’t have to know anything about the complexity or even the number of the original set of requests. This is great for batching.
You can do some nice tricks with multiple promises http://stackoverflow.com/a/6162959/373007
I use this in ownCloud contacts app to know when all address books are loaded.
They’ve had this functionality since version 1.5, which came out over 3 years ago. Don’t make it sound like it’s new.
New to me?
Probably like many devs, it’s completely new to me too and I’ve used jquery for ages, so thank-you for sharing! Some people like to show they know more than others, best to just ignore’em :)
Yea, Joe is right, it was it came about three years ago. But I don’t think David tries to show that it is some kind of latest discovery or something! Well, let it go, overall it is a good practice but most of the devs are unaware of it, so thanks for such a nice sharing :)
Oh, very nice, thanks for sharing this with us!
Nice find – just what I was looking for!
It is awesome trick, Still working with my new project. Thanks david.
Another useful pattern:
$.when.apply($, arrayOfDeferreds)
That allows you to create the batch of Deferreds elsewhere, and have it be of any length (so that the batching call doesn’t have to know or care), as long as your
.then
handler knows how to recombine the resulting requests.I’ve been using this for a while now, but good to know. Another useful trick I use with this and
$.Deferreds
in general to manage what value is returned when the deferred is resolved. One way I do this is to wrap this in a function like so:Then you can use the
handler
function just like a regular deferred and you’re also encapsulating the arguments being passed when the deferred resolves. You could achieve similar encapsulation by usingdeferred.pipe
. Then you don’t have to manage another deferred.You should return the promise instead of the deferred object, otherwise the caller could easily change the deferred object (what you don’t want…).
when doing:
is the order promised? will
a.js
always be loaded beforeb.json
?so a year and a half after you wrote this blog I still found it and used it…