Creating Responsive Applications Using jQuery Deferred and Promises
Julian Aubourg, Addy Osmani | March 24, 2011
At a high-level, deferreds can be thought of as a way to represent costly operations which can take a long time to complete. They’re the asynchronous alternative to blocking functions and the general idea is that rather than your application blocking while it awaits some request to complete before returning a result, a deferred object can instead be returned immediately. You can then attach callbacks to the deferred object: they will be called once the request has actually completed.
In its most basic form, a ‘promise’ is a model that provides a solution for the concept of deferred (or future) results in software engineering. The main idea behind it is something we’ve already covered: rather than executing a call which may result in blocking, we instead return a promise for a future value that will eventually be satisfied.
If it helps to have an example here, consider that you are building a web application which heavily relies on data from a third party API. A common problem that’s faced is having an unknown knowledge of the API server's latency at a given time so it’s possible that other parts of your application may be blocked from running until a result from it is returned. Deferreds provide a better solution to this problem, one which is void of 'blocking' effects and completely decoupled.
The Promise/A proposal defines a method called 'then' that can be used to register callbacks to a promise and, thus, get the future result when it is available. The pseudo-code for dealing with a third party API that returns a promise may look like:
Furthermore, a promise can actually end up being in two different states:
Thankfully, the 'then' method accepts two parameters: one for when the promise was resolved, another for when the promise was rejected. If we get back to pseudo-code, we may do things like:
In the case of certain applications, it is necessary to have several results returned before your application can continue at all (for example, displaying a dynamic set of options on a screen before a user is able to select the option that interests them).Where this is the case, a method called 'when' exists, which can be used to perform some action once all the promises have been fully fulfilled:
A good example is a scenario where you may have multiple concurrent animations that are being run. Without keeping track of each callback firing on completion, it can be difficult to truly establish once all your animations have finished running. Using promises and 'when' however this is very straight-forward as each of your animations can effectively say ‘we promise to let you know once we're done'. The compounded result of this means it's a trivial process to execute a single callback once the animations are done. For example:
This means that one can basically write non-blocking logic that can be executed without synchronization. Rather than directly passing callbacks to functions, something which can lead to tightly coupled interfaces, using promises allows one to separate concerns for code that is synchronous or asynchronous.
In the next section we'll be looking at jQuery's implementation of deferreds, which you may find significantly easier to appreciate now that you've reviewed promises.
jQuery's implementation of deferreds, first introduced in jQuery 1.5, offers a solution which doesn't hugely differ from the section above describing the high-level concept of promises - in principle, you are given the ability to 'defer' the return of a result to some point in the future, which wasn't previously possible using the library alone.
Deferreds were added as a part of a large rewrite of the ajax module, led by Julian following the CommonJS Promises/A design. Whilst 1.5 and above include deferred capabilities, former versions of jQuery had jQuery.ajax() accept callbacks that would be invoked upon completion or error of the request, but suffered from heavy coupling - the same principle that would drive developers using other languages or toolkits to opt for deferred execution.
In practice what jQuery's version provides you with are several enhancements to the way callbacks are managed, giving you significantly more flexible ways to provide callbacks that can be invoked whether the original callback dispatch has already fired or not. It is also worth noting that jQuery's Deferred object supports having multiple callbacks bound to the outcome of particular tasks (and not just one) where the task itself can either be synchronous or asynchronous.
You may find the following table of Deferred features useful for understanding what supported features allow you to achieve:
You are able to use Deferred objects in conjunction with the promise concept of when(), implemented in jQuery as $.when() to wait for all of the Deferred object's requests to complete executing (ie. for all of the promises to be fulfilled). In technical terms, $.when() is effectively a way to execute callbacks based on any number of objects (which can either be Deferred objects or otherwise) that represent asynchronous events.
An example of $.when() accepting multiple arguments can be seen below in conjunction with the handler we've previously discussed, .then():
The $.when() implementation offered in jQuery is quite interesting as it not only interprets deferred objects, but when passed arguments that are not deferreds, it treats these as if they were resolved deferreds and executes any callbacks (doneCallbacks) right away. It is also worth noting that jQuery's deferred implementation, in addition to exposing deferred.then() also supports the deferred.done() and deferred.fail() methods which can also be used to add callbacks to the deferred’s queues.
We will now take a look at a code example that utilizes many of the deferred features mentioned in the table presented earlier. Here we're creating a very basic application that consumes (1) an external news feed and (2) a reactions feed for pulling in the latest comments via $.get() (which will return a promise). The application also has a function (prepareInterface()) which returns a promise to complete animating our containers for both the news and reactions.
To ensure all three of these steps are resolved before executing additional behaviour related to them, we make use of $.when(). The .then() and .fail() handlers can then be used to execute additional application logic depending on your needs.
Further Deferreds examples
So deferreds are used behind the hood in Ajax but it doesn't mean they can’t also be used elsewhere. In this section, we'll look at some situations where deferreds will help abstract away asynchronous behaviour and decouple our code.
We all had to code some kind of caching mechanism at one point or another in our career, and truth is we actually wrote a lot of them.
When it comes to asynchronous tasks, caching can be a bit demanding since you have to make sure a task is only performed once for a given key. As a consequence, the code has to somehow keep track of inbound tasks. For instance, if we take the following snippet:
The caching mechanism has to make sure the url is only requested once even if the script isn't in cache yet. So we end up writing some logic to keep track of callbacks bound to a given url in order for the cache system to properly handle both complete and inbound requests.
Thankfully, that's exactly the kind of logic deferreds implement so we can do something like this:
The code is pretty straight-forward: we cache one promise per url. If there is no promise for the given url yet, then we create a deferred and issue the request. If it already exists, however, we simply attach the callback to it. The big advantage of this solution is that it will handle both complete and inbound requests transparently. Another advantage is that a deferred-based cache will deal with failure gracefully. The promise will end up rejected which we can test for by providing an error callback:
Remember: the snippet above will work whether the request is inbound, complete or not issued yet!
Generic asynchronous cache
It is also possible to make the code completely generic and build a cache factory that will abstract out the actual task to be performed when a key isn't in the cache yet:
Now that the request logic is abstracted away, we can re-write cachedGetScript as follows:
This will work because every call to createCache will create a new cache repository and return a new cache-retrieval function.
Now that we have this generic cache factory, it's pretty easy to implement all sorts of logics when it comes to requesting a value not yet in cache.
An immediate candidate is image loading: we may need to load images yet would like to make sure we don't load the same image twice. It's pretty easy to do with createCache:
Again, the following snippet:
will work regardless of whether my-image.png has already been loaded or not, or if it is actually in the process of being loaded.
Caching Data API responses
API requests that are considered immutable during the lifetime of your page are also perfect candidates. For instance, the following:
will allow you to perform searches on Twitter and cache them at the same time:
This deferred-based cache is not limited to network requests; it can also be used for timing purposes.
For instance, you may need to perform an action on the page after a given amount of time so as to attract the user's attention to a specific feature they may not be aware of or deal with a timeout (for a quiz question for instance). While setTimeout is good for most use-cases it doesn't handle the situation when the timer is asked for later, even after it has theoretically expired. We can handle that with the following caching system:
The new afterDOMReady helper method provides proper timing after the DOM is ready while ensuring the bare minimum of timers will be used. If the delay is already expired, any callback will be called right away.
Synchronizing multiple animations
Animations are another example of popular asynchronous tasks. However, executing code after several unrelated animations have completed can be a little challenging.
Though a means to retrieving a promise on animating elements is in the works for jQuery 1.6, jQuery 1.5.x doesn't have such a facility but it's pretty easy to manually code one:
We can then synchronize between different animations using jQuery.when:
We can also use the same trick to create some helper methods:
And then synchronize between animations using the new helpers as follows:
While jQuery offers all the event binding one may need, it can become a bit cumbersome to handle events that are only supposed to be dealt with once.
For instance, you may wish to have a button that will open a panel the first time it is clicked and leave it open afterwards or take special initialization actions the first time said button is clicked. When dealing with such a situation, one usually end up with code like this:
then, later on, you may wish to take actions, but only if the panel is opened:
This is a very coupled solution. If you want to add some other action, you have to edit the bind code or just duplicate it all. If you don't, your only option is to test for buttonClicked and you may lose that new action because the buttonClicked variable may be false and your new code may never be executed.
We can do much better using deferreds (for simplification sake, the following code will only work for a single element and a single event type, but it can be easily generalized for full-fledged collections with multiple event types):
The code works as follows:
While the code is definitely more verbose, it makes dealing with the problem at hand much simpler in a compartmentalized and decoupled way. But let's define a helper method first:
Then the logic can be re-factored as follows:
If we need to perform some action only when the panel is opened later on, all we need is this:
Nothing is lost if the panel isn't opened yet, the action will just get deferred (see what we did here?) until the button is clicked.
All of the samples above can seem a bit limited when looked at separately. However, the true power of promises comes into play when you mix them together.
Requesting panel content on first click and opening said panel
Let's say we have a button that opens a panel, requests its content over the wire than fades the content in. Using the helpers we defined earlier, we could do something like this:
Loading images in a panel on first click and opening said panel
Let's say we already have the content of the panel but we only want to load images within it when the button is clicked and we want to fade the content of the panel in only once all images have been loaded.
The html code for this would look something like:
We use the data-src attribute to keep track of the real image location. The code to handle our use case using our promise helpers is as follows:
The trick here is to keep track of all the loadImage promises. We later join them with the panel slideDown animation using $.when. So when the button is first clicked, the panel will slideDown and the images will start loading. Once the panel has finished sliding down and all the images have been loaded, then, and only then, will the panel fade in.
Loading images on the page after a specific delay
Let's say we want to implement deferred (pun pun) image display on the entire page. To do so, we require the following format in HTML:
What it says is pretty straight-forward:
How would we implement this?
If we wanted to delay the loading of the images themselves, we would do things a bit differently:
Here, we first wait for the delay to be fulfilled before attempting to load the image. It can make a lot of sense when you want to limit the number or network requests on page load.
As you can see, promises can be very useful even without any ajax requests involved. Using the deferred implementation in jQuery 1.5, it's quite easy to separate asynchronous tasks definitions from any code depending on them. That way, you can decouple logic into your applications pretty easily.
About the Author
Find Julian on:
About the Author
Find Addy on: