Link to wealthfront.com

Fork me on GitHub

Tuesday, December 4, 2012

jQuery.Deferred is the most important client-side tool you have

jQuery's introduction of $.Deferred() is one of the library's most powerful additions in recent history. It's not a new idea, but it merits an introduction now that it's available to thousands of client-side developers. At its core, the Deferred pattern is a simple but powerful tool for the management of asynchronous processes. As we all know, in client-side development, these asynchronous situations are everywhere.
We're going to take a look at deferreds and the API offered by jQuery's implementation. There are plenty of examples below, so follow along. By the end of this article you'll know what deferreds are, and when to use them.

Understanding the core concept

The "Deferred" pattern describes an object which acts as a proxy for some unit of computation that may or may not have completed. The pattern can apply to any asynchronous process: AJAX requests, animations, or web workers to name a few. Even user behavior can be thought of as a "delayed computation."
In their simplest form, deferreds allow you to specify what will occur when a computation completes or fails. jQuery's specific implementation allows you to register callback functions that will run if a deferred resolves successfully, if it is rejected with errors, or if it is notified of some "progress" towards a resolved state (more on that later).
You've probably been using deferreds already. jQuery's AJAX methods return an object that implements the deferred interface. They resolve when the AJAX request succeeds, and are rejected if the HTTP request fails.

The single most important thing you need to understand

Deferreds help abstract away the "when" of your asynchronous processes. Deferreds can be passed around indefinitely, and callbacks can continue to be added during the entire lifetime of the deferred object. The key to this behavior is callbacks registered with the deferred will run immediately if the deferred is already resolved. You don't need to worry if the asynchronous unit of computation (e.g. an AJAX request) is finished or not. Simply bind a callback to the deferred and it will either run if the deferred is already resolved, or run when the deferred is resolved in the future.

Using Deferreds with jQuery

Resolution and Rejection

The core Deferred methods you'll interact with deal with the resolution or rejection of the deferred. You can create a new deferred object with $.Deferred(). That object's done() and fail() both accept functions that will be executed when the deferred is resolved or rejected respectively. Actually resolving or rejecting a deferred can be accomplished with resolve() and reject(). Internally, jQuery's ajax() method will call resolve() on the deferred it returns when the request completes successfully, and reject() if the request fails (for example, a 404 HTTP response).
Remember, if you attach a done or fail handler to a deferred that has already resolved, that function immediately executes.

Notifying Deferreds: notify() and progress()

jQuery 1.7 also added the concept of progress to a deferred, in addition to rejection and resolution. progress() allows you to attach callbacks that are executed when notify() is called on the deferred. This allows the deferred to represent the concept of progress towards a resolved state. A deferred representing a long-loading resource could use a callback registered with progress() to periodically update a progress bar, for example. The deferred would be notified during loading, and resolved when loading is complete.

Returning a promise()

In most cases, if you're returning a deferred you don't want the consumer of your deferred to be able to resolve or reject it — you want to manage that yourself. In that case you return a promise. In jQuery's terminology, a promise is a read-only deferred. A promise allows you to attach callbacks and ask the deferred's status, but you can't tell it to change its state (e.g. resolve, reject). jQuery's ajax() method returns a promise, since its up to the internal code handling the AJAX request to decide whether the request succeeded or failed.

Synchronizing asynchronous events with when()

$.when() accepts one or more deferreds and produces a new deferred that will only resolve when all the supplied deferreds resolve. This allows you to combine multiple asynchronous events into one.
Consider the following example:
Our UI requires data from two separate AJAX requests, and it needs the data from both requests before it can render itself.
Without when(), we're forced to nest callbacks to ensure both requests have finished before rendering. What's worse, we have two different places to specify error handling since either request could fail:

Instead, we can use $.when() to combine the two deferreds returned by our AJAX requests into one. This deferred will only resolve when both requests have completed. We can register a function that handles the rendering of our UI element as a success callback. Additionally, we only need to specify a failure handler in a single place.

Transformations with pipe()

Perhaps the most interesting item in jQuery's Deferred API is pipe(). pipe() allows you to apply a transformation (jQuery calls them "filters") to the result of a deferred. The function you provide can modify the value that reject() or resolve() passes to your callbacks, or it can modify the state of the deferred itself, changing which callbacks get called on the new "piped" deferred.
Let's look at an example. Consider the following situation:
Your JSON API represents errors with a flag in the JSON response (e.g. {error:true}) instead of sending an HTTP status code that indicates failure. Your JSON API always returns with an HTTP status of 200, error or not.
In this situation, if an error occurs the response still comes back with an http status of 200 ("OK"). Since the promises returned from jQuery's AJAX methods are only rejected if the HTTP request fails, you have to do your error handling in the success callback, not the fail callback. Here's how this might look:

Obviously this is not an ideal situation. By leveraging the power of pipe() we can create a new deferred that intercepts the resolution of the AJAX request. We'll check the "error" flag in our JSON response. If it's true, then the pipe will actually return a rejected deferred which will cause failure callbacks to be executed. Here's an example illustrating this use of pipe:

People are asynchronous too

The truth is, any number of things on your web site or application are asynchronous. This can even include your users themselves. Consider the following situation.
Your site asks users to create a profile. To encourage them to fill out their profile fully, you display a progress meter indicating how close they are to completion. As they fill it out, you'd like the progress meter to change, and when they finish you'd like to show a "thank you message."
In this situation the deferred represents the delayed completion of the profile. The entity that is creating or "computing" the profile is, in fact, a human being. It might not be a situation you normally consider "asynchronous", but the truth is this is a perfectly valid situation to represent with a deferred. We can use notify() to notify the deferred of user actions (remember, this indicates progress toward resolution), and resolve() to notify the deferred that the profile (and thus the item that the deferred is a proxy for) is complete. We'll bind callbacks using progress() to update our progress indicator, and use done() to bind a callback that shows our thank-you message.
This fiddle shows a complete, functioning example:

Deferreds everywhere

Dealing with asynchronous processes is a fact of life for most programmers, and client-side developers are no exception. The deferred pattern is simple, powerful, and widely applicable, but don't take my word for it: