Tuesday, February 21, 2012

Synchronizing multiple jQuery ajax calls using the when function

There is a lot of information about jQuery's ajax() function the jqXHR object and using jqXHR as a Deferred object with the when() function, but I couldn't find any examples that illustrated all this functionality working together.

The problem I solved using ajax() and when() is not unique or unusual, I had an unknown number of simultaneous ajax() calls to make and wanted code execution to continue on a single callback function when they had all completed. This is exactly the type of situation that Deferred objects and the when() function were created to handle. If you are used to working in multi-threaded environments you can think of when() as a kind of synchronization object. You can pass multiple jqXHR objects, returned by ajax() functions, to when(). You can chain a then() or done() function to the Deferred object that when() returns and the callback function you pass as an argument to the chained function will be invoked after all of the ajax() calls have completed.

Something I wanted to do to solve my problem but didn't know how to do, or even if it was possible, was to pass an unknown number of jqXHR objects to when() and get back the results for each jqXHR. One of the strange and wonderful things about javascript is that functions are objects and as objects they can, and do, have their own functions! Another peculiarity is that Javascript functions will take any number of arguments, regardless of how the function is originally defined, this matters because it means when() can take any number of Deferred objects as parameters. If you know each of the Deferred objects ahead of time you can simply pass them to when() as parameters (e.g. $.when(deferred1, deferred2)). If you don't know the number of objects you can gather them together in an array and pass it to when() using the apply function. The apply function takes an array of objects and passes them to its owner function as a set of parameters (be warned that the number of parameters a function can accept is going to be limited by the javascript environment, don't go crazy).

Now I knew how to pass an unknown number of jqXHR objects to when(). How do I get the results of the ajax() calls? The when() function returned a Deferred object and I passed its done() function a callback function to invoke when all the ajax() calls were, well, done. I couldn't find any examples of what this callback function should look like when an array of Deferred objects were passed to when(). My initial thought was that an array of result objects would be passed to the callback, but the single argument I defined only had the result of the first ajax() call. After a long time of experimentation, fruitless searching, and head scratching I suddenly remembered the arguments local variable that is available in every function. I realized that my callback function was being passed a result object as a separate parameter for each ajax() call. Sure enough I found that the length of the callback function's arguments matched the number of jqXHR objects passed to when() and that iterating over it I could get a result object for each call.

So finally here is a code snippet illustrating how to pass an array of jqXHR objects to when() and get back the results! It's easier to look at the original Gist or you can try it out in jsfiddle to see it work.

One final technical note: the jqXHR is not really a Deferred object, it implements the Promise interface but for the sake of simplicity I called it a Deferred object.

1 comment:

  1. Exactly the solution I was looking for on a little project I'm working on just now.