Creating Responsive Applications Using jQuery Deferred and Promises
Julian Aubourg, Addy Osmani | March 24, 2011
Today we’re going to explore the concept of deferreds - an important feature found in JavaScript toolkits such as dojo and MochiKit, which recently also made its debut to popular JavaScript library jQuery. Deferreds offer a solution for abstracting non-blocking elements (such as the response to ajax requests) and achieve this by creating a ‘promise’ which aims to return a response at some point in the future. If you haven’t come across the concept of ‘promises’ before, we'll be covering them in some detail shortly.
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.
Promises
Some of the material you may read on promises and deferreds may focus on quite implementation-specific applications of it. In this section, we’ll be providing you with a general understanding of how promises work which can be applied to almost any JavaScript framework that supports a variation of deferreds.
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:
promise = callToAPI( arg1, arg2, ...);
promise.then(function( futureValue ) {
/* handle futureValue */
});
promise.then(function( futureValue ) {
/* do something else */
});
Furthermore, a promise can actually end up being in two different states:
- resolved: in which case data is available
- rejected: in which case something went wrong and no value is available
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:
promise.then( function( futureValue ) {
/* we got a value */
} , function() {
/* something went wrong */
} );
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:
when(
promise1,
promise2,
...
).then(function( futureValue1, futureValue2, ... ) {
/* all promises have completed and are resolved */
});
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:
when(
function(){
/* animation 1 */
/* return promise 1 */
},
function(){
/* animation 2 */
/* return promise 2 */
}
).then(function(){
/* once both animations have completed
we can then run our additional logic */
});
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 Deferreds
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:
jQuery.Deferred() | A constructor that creates a new Deferred object, can take an optional initFunction parameter that will be called right after the deferred has been constructed. |
jQuery.when() | A way to execute callback functions based on one or more objects that represent asynchronous tasks. |
jQuery.ajax() | Performs an asynchronous Ajax requests where the jqXHR objects returned by $.ajax() implement the Promise interface, giving them all the properties, methods, and behaviour of a Promise. |
deferred.then(resolveCallback,rejectCallback) | Handlers to be called when the Deferred object is resolved or rejected. |
deferred.done() | Functions or array of functions that are called when the Deferred object is resolved. |
deferred.fail() | Functions or array of functions that are called when the Deferred is rejected. |
deferred.resolve(arg1, arg2, ...) | Resolve a Deferred object and call any 'done' Callback with the given arguments. |
deferred.resolveWith(context,args) | Resolve a Deferred object and call any 'done' Callback with the given context and arguments. |
deferred.isResolved | Determine whether a Deferred object has been resolved. |
deferred.reject(arg1, arg2, ...) | Reject a Deferred object and call any 'fail' Callback with the given arguments. |
deferred.rejectWith(context,args) | Reject a Deferred object and call any 'fail' Callback with the given context and arguments. |
deferred.promise() | Returns a promise, that is an immutable view of the deferred object: the promise can be passed around safely since the underlying deferred cannot be resolved or rejected through it. |
At the heart of jQuery's implementation is jQuery.Deferred - a chainable constructor which is able to create new deferred objects that can check for the existence of a promise to establish whether the object can be observed. It can also invoke callback queues and pass on the success of synchronous and asynchronous functions. It's quite essential to note that the default state of any Deferred object is something is unresolved. Callbacks which may be added to it through .then() or .fail() are queued up and get executed later on in the process.
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():
function successFunc(){
console.log( “success!” );
}
function failureFunc(){
console.log( “failure!” );
}
$.when(
$.ajax( "/main.php" ),
$.ajax( "/modules.php" ),
$.ajax( “/lists.php” )
).then( successFunc, failureFunc );
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.
function getLatestNews() {
return $.get( “latestNews.php”, function(data){
console.log( “news data received” );
$( “.news” ).html(data);
} );
}
function getLatestReactions() {
return $.get( “latestReactions.php”, function(data){
console.log( “reactions data received” );
$( “.reactions” ).html(data);
} );
}
function prepareInterface() {
return $.Deferred(function( dfd ) {
var latest = $( “.news, .reactions” );
latest.slideDown( 500, dfd.resolve );
latest.addClass( “active” );
}).promise();
}
$.when(
getLatestNews(),
getLatestReactions(),
prepareInterface()
).then(function(){
console.log( “fire after requests succeed” );
}).fail(function(){
console.log( “something went wrong!” );
});
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.
Caching
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.
Asynchronous cache
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:
$.cachedGetScript( url, callback1 );
$.cachedGetScript( url, callback2 );
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:
var cachedScriptPromises = {};
$.cachedGetScript = function( url, callback ) {
if ( !cachedScriptPromises[ url ] ) {
cachedScriptPromises[ url ] = $.Deferred(function( defer ) {
$.getScript( url ).then( defer.resolve, defer.reject );
}).promise();
}
return cachedScriptPromises[ url ].done( callback );
};
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:
$.cachedGetScript( url ).then( successCallback, errorCallback );
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:
$.createCache = function( requestFunction ) {
var cache = {};
return function( key, callback ) {
if ( !cache[ key ] ) {
cache[ key ] = $.Deferred(function( defer ) {
requestFunction( defer, key );
}).promise();
}
return cache[ key ].done( callback );
};
}
Now that the request logic is abstracted away, we can re-write cachedGetScript as follows:
$.cachedGetScript = $.createCache(function( defer, url ) {
$.getScript( url ).then( defer.resolve, defer.reject );
});
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.
Image loading
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:
$.loadImage = $.createCache(function( defer, url ) {
var image = new Image();
function cleanUp() {
image.onload = image.onerror = null;
}
defer.then( cleanUp, cleanUp );
image.onload = function() {
defer.resolve( url );
};
image.onerror = defer.reject;
image.src = url;
});
Again, the following snippet:
$.loadImage( "my-image.png" ).done( callback1 );
$.loadImage( "my-image.png" ).done( callback2 );
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:
$.searchTwitter = $.createCache(function( defer, query ) {
$.ajax({
url: "https://search.twitter.com/search.json",
data: {
q: query
},
dataType: "jsonp",
success: defer.resolve,
error: defer.reject
});
});
will allow you to perform searches on Twitter and cache them at the same time:
$.searchTwitter( "jQuery Deferred", callback1 );
$.searchTwitter( "jQuery Deferred", callback2 );
Timing
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:
var readyTime;
$(function() {
readyTime = jQuery.now();
});
$.afterDOMReady = $.createCache(function( defer, delay ) {
delay = delay || 0;
$(function() {
var delta = $.now() - readyTime;
if ( delta >= delay ) {
defer.resolve();
} else {
setTimeout( defer.resolve, delay - delta );
}
});
});
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:
$.fn.animatePromise = function( prop, speed, easing, callback ) {
var elements = this;
return $.Deferred(function( defer ) {
elements.animate( prop, speed, easing, function() {
defer.resolve();
if ( callback ) {
callback.apply( this, arguments );
}
});
}).promise();
};
We can then synchronize between different animations using jQuery.when:
var fadeDiv1Out = $( "#div1" ).animatePromise({
opacity: 0
}),
fadeDiv2In = $( "#div1" ).animatePromise({
opacity: 1
}, "fast" );
$.when(
fadeDiv1Out,
fadeDiv2In
).done(function() {
/* both animations ended */
});
We can also use the same trick to create some helper methods:
$.each([
"slideDown",
"slideUp",
"slideToggle",
"fadeIn",
"fadeOut",
"fadeToggle"
], function( _, name ) {
$.fn[ name + "Promise" ] = function( speed, easing, callback ) {
var elements = this;
return $.Deferred(function( defer ) {
elements[ name ]( speed, easing, function() {
defer.resolve();
if ( callback ) {
callback.apply( this, arguments );
}
});
}).promise();
};
});
And then synchronize between animations using the new helpers as follows:
$.when(
$( "#div1" ).fadeOutPromise(),
$( "#div2" ).fadeInPromise( "fast" )
).done(function() {
/* both animations are done */
});
One-time event
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:
var buttonClicked = false;
$( "#myButton" ).click(function() {
if ( !buttonClicked ) {
buttonClicked = true;
initializeData();
showPanel();
}
});
then, later on, you may wish to take actions, but only if the panel is opened:
if ( buttonClicked ) {
/* perform specific action */
}
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):
$.fn.bindOnce = function( event, callback ) {
var element = $( this[ 0 ] ),
defer = element.data( "bind_once_defer_" + event );
if ( !defer ) {
defer = $.Deferred();
function deferCallback() {
element.unbind( event, deferCallback );
defer.resolveWith( this, arguments );
}
element.bind( event, deferCallback )
element.data( "bind_once_defer_" + event , defer );
}
return defer.done( callback ).promise();
};
The code works as follows:
- check if the element already has a deferred attached for the given event
- if not, create it and make it so it is resolved when the event is fired the first time around
- then attach the given callback to the deferred and return the promise
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:
$.fn.firstClick = function( callback ) {
return this.bindOnce( "click", callback );
};
Then the logic can be re-factored as follows:
var openPanel = $( "#myButton" ).firstClick();
openPanel.done( initializeData );
openPanel.done( showPanel );
If we need to perform some action only when the panel is opened later on, all we need is this:
openPanel.done(function() {
/* perform specific action */
});
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.
Combining helpers
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:
$( "#myButton" ).firstClick(function() {
var panel = $( "#myPanel" );
$.when(
$.get( "panel.html" ),
panel.slideDownPromise()
).done(function( ajaxResponse ) {
panel.html( ajaxResponse[ 0 ] ).fadeIn();
});
});
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:
<div id="myPanel">
<img data-src="image1.png" />
<img data-src="image2.png" />
<img data-src="image3.png" />
<img data-src="image4.png" />
</div>
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:
$( "#myButton" ).firstClick(function() {
var panel = $( "#myPanel" ),
promises = [];
$( "img", panel ).each(function() {
var image = $( this ),
src = element.attr( "data-src" );
if ( src ) {
promises.push(
$.loadImage( src ).then( function() {
image.attr( "src", src );
}, function() {
image.attr( "src", "error.png" );
} )
);
}
});
promises.push(
panel.slideDownPromise()
);
$.when.apply( null, promises ).done(function() {
panel.fadeIn();
});
});
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:
<img data-src="image1.png" data-after="1000" src="placeholder.png" />
<img data-src="image2.png" data-after="1000" src="placeholder.png" />
<img data-src="image1.png" src="placeholder.png" />
<img data-src="image2.png" data-after="2000" src="placeholder.png" />
What it says is pretty straight-forward:
- load image1.png and show it immediately for the third image and after one second for the first one
- load image2.png and show it after one second for the second image and after two seconds for the fourth image
How would we implement this?
$( "img" ).each(function() {
var element = $( this ),
src = element.attr( "data-src" ),
after = element.attr( "data-after" );
if ( src ) {
$.when(
$.loadImage( src ),
$.afterDOMReady( after )
).then(function() {
element.attr( "src", src );
}, function() {
element.attr( "src", "error.png" );
} ).done(function() {
element.fadeIn();
});
}
});
If we wanted to delay the loading of the images themselves, we would do things a bit differently:
$( "img" ).each(function() {
var element = $( this ),
src = element.attr( "data-src" ),
after = element.attr( "data-after" );
if ( src ) {
$.afterDOMReady( after, function() {
$.loadImage( src ).then(function() {
element.attr( "src", src );
}, function() {
element.attr( "src", "error.png" );
} ).done(function() {
element.fadeIn();
});
} );
}
});
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.
Conclusions
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
Julian Aubourg is a Software Architect currently based in Brussels, Belgium. Co-founder of Creative-Area, Julian just loves to design crazy middlewares in JavaScript and PHP. As a member of the jQuery core team, Julian lead the rewrite of the ajax module and the introduction of deferreds in jQuery 1.5.
Find Julian on:
- Twitter: @jaubourg
About the Author
Addy Osmani is a User Interface Developer from London, England. An avid blogger, he has a passion for encouraging best practices in client-side development, in particular with respect to JavaScript and jQuery. He enjoys evangelizing the latter of these and is on the jQuery Bug triage, API and front-end teams. Addy works at AOL where he's a JavaScript developer on their European Products & Innovation team.
Find Addy on:
- Addy's Blog
- Twitter: @addyosmani