JQuery Promises and Deferreds: I promise this will be short

Graham Jenson
Maori Geek
Published in
7 min readJan 31, 2014

--

I am always looking for beautiful solutions to complex problems, and recently I have been experimenting with promises to solve the ugly problem of asynchronous actions in javascript. Promises are a simple metaphor that make complex operations easy to understand. In this post I will describe what promises are, why they are beneficial, how to use them, and a project I have been working on called Back-on-Promise which integrates promises into backbone.js.

Note: this post will focum Science on How to Test your Codes on the JQuery 1.8 and up promises API, with a small discussion at the end about other libraries

What are Promises and Deferred Objects?

To understand what promises are you need to understand deferred objects. A deferred object describes the state of availability of something, and offers a nice interface to help when that state changes.

A deferred object starts in a pending state, this means it is not yet completed. While in the pending state if the resolve() function is called then the state is changed to resolved, and if the reject() function is called then the state is changed to rejected.

$.Deferred().state() // 'pending'
$.Deferred().resolve().state() // 'resolved'
$.Deferred().reject().state() // 'rejected'

Callbacks can be attached to these state changes using the done() and fail() functions, or to all state changes using the always() function. The arguments given to the resolve() or the fail() functions are passed to the callbacks so the object that resolved or failed the deferred object can be used.

For example:

log = function () {
console.log(Array.prototype.slice.call(arguments).join(' '))
} // logs a functions arguments
d = $.Deferred()
d.done(log)
d.resolve('maorigeek') // maorigeek

All these functions return the deferred object so they can be chained together to make the code more concise, like this:

d = $.Deferred().done(log).resolve('maorigeek') // maorigeek

One last thing to understand is that attaching a callback after the state has changed will execute the right callbacks immediately. This means you do not need to worry when a deferred object is resolved or rejected, and that the correct arguments will be passed to the callbacks at any time. For example:

d = $.Deferred()
d.resolve('maorigeek')
d.done(log) // maorigeek

Promises

It rarely makes sense to resolve or reject deferred objects that a third-party has made. For example, resolving an AJAX call before the server has returned or rejected the request. This is where promises are used. A promise is just a deferred object that does not have functions to change its state. By using a promise instead of a deferred object it will stop others breaking promises you made. To get a promise for a deferred object, you only need to call its promise() function, e.g. p = $.Deferred().promise().

There are a few helper methods to deal with promises and deferred objects.

The when() function takes a list of deferreds and returns a single promise that will resolve once all its arguments are resolved.

It will also accept variables as arguments and wrap them in an already resolved promise.

p = $.when("maorigeek")
p.state() // resolved
p.done(log) // maorigeek

Therefore, when() is like a logical AND and will wait for all deferreds to resolve. For example:

d = $.Deferred()
$.when(d, 'World').done(log)
d.resolve('Hello') // Hello World

The then() function takes two callbacks, one that that will be executed if the promise is resolved, the other if it is rejected; and returns a new promise. If the callback returns a deferred object then the returned promise will be of that deferred object.

gate = $.Deferred()
promise = $.when('Hello').then(function(h){
return $.when(h,gate)
})
promise.done(log)
gate.resolve('World') // Hello World

The above example first creates a deferred object called gate, which is used as a switch. Using when() a promise for 'Hello' (which is already resolved) is created. Then using then(), a callback is attached that returns a promise for the input and gate (the returned promise will not be resolved till gate is resolved). Finally, a resolved callback is attached to the proimse that logs the input.

Once gate becomes resolved the promise returned by then() becomes resolved, and the final log callback is executed.

Why use promises?

Now that you understand what promises are, why should you use them? To demonstrate the advantages of using promises I am going to use the a contrived (borrowed) example of requiring to wait for three asynchronous requests to render a sidebar on a website. Here are three possible solutions to that problem...

The bad, the ugly, and the good

Given of waiting on three AJAX calls before rendering a side-bar, there are a few different ways to go...

Serial Calling Ajax (bad) a.k.a. callback hell

$.ajax({
success: function() {
$.ajax({
success: function() {
$.ajax({
//Yo Dawg, I heard you like callbacks...
});
});
}
});

This is not pretty, efficient, or smart. It fetches the data serially, it will quickly get out of hand when handling errors or branches, and it is difficult to refactor or understand.

Parallel with call-backs (ugly)

var pseudo_promises = [];$.ajax({
success: function() {
pseudo_promises.push('resolved');
check();
}
});
$.ajax({
success: function() {
pseudo_promises.push('resolved');
checkDataCalls();
}
});
$.ajax({
success: function() {
pseudo_promises.push('resolved');
check();
}
});
var check = function() {
//checks for all 3 values in the pseudo_promises array, then
//renders sidebar
}

This is nearly as good as code that uses callbacks can get. It fetchs the data in parallel, separates the functions into readable and refactorable functions. However, it is verbose, difficult to expand, and brittle, as handling errors in this fashion will introduce significantly more complexity across all functions.

Promises in Action (good)

var address = $.ajax({});var tweets = $.ajax({});var facebook = $.ajax({});render_side_bar = function(address, tweets, facebook){
//render sidebar
}
render_no_side_bar = function(){}$.when(address, tweets, facebook).then(
render_side_bar,
render_no_side_bar
)

This is where promises make understanding your code easier. First we make three AJAX calls to get the address, tweets and facebook, when each has resolved then render_side_bar if one failed, render_no_side_bar.

Fun with Promises

Great! Now you know what promises are and I have convinced you of their ability to help clean up asynchronous code, but what situations can they be used in? Here I will describe some useful (and fun) things that you can do with promises.

Complex animations

Not knowing when an animation finishes but having to do something when it completes (e.g. another animation) can result in code that is really difficult to read. Especially if it involves other operations like rendering, or form manipulation. JQuery will return a promise (if you ask) for most of its animations to easily chain them together

$('body').toggle('blinds').promise().then(
function(){
$('body').toggle('blinds')
}
)

The wait promise

A promise can be used to wrap existing functions to increase the vocabulary of promises. Chris Webb uses promises to redefine setTimeout to make using it significantly more understandable.

function wait(ms) {
var deferred = $.Deferred();
setTimeout(function(){deferred.resolve()}, ms);
return deferred.promise();
}
wait(1500).then(function () {
// After 1500ms this will be executed
});

Handling a Queue

This may not be a complete (good) idea, but promises can be used to manage a queue of events, e.g.:

window.queue = $.when()
$('#list').on('click', function() {
window.queue = window.queue.then(function() {
//do the thing
})
}
)

Here is an example I made that has a reasonably complex animation when you click on it. To make sure that the animation executes once for all clicks, and that it finishes before executing the next clicks animation, I use the above method for event queueing and execution.

http://bl.ocks.org/grahamjenson/raw/8309901/

Back on Promise

Note: back-on-promise is not production ready, with further interest I may be able to change that.

To have some fun with promises I wanted to see if I could implement an extension to Backbone that would allow model a user to get a remote model with minimal code. This turned into back-on-promise (github) an npm module that is still very beta. The main idea is to change Backbone.Model.get to return a promise for the data you want, whether it is async. or not. This way the user of the model does not need to care if the model needs to go to the server and fetch the data. It also caches the data in the resolved promise so that the data does not need to be fetched twice.

For example:

class Posts extends Backbone.Collection
url: -> "http://user/#{@user.id}/posts"
model: Post
class User extends BOP.BOPModel
@has 'posts', Posts, method: 'fetch', reverse: 'user'
user = new User(id: 1)
$.when(user.get('posts')).done( (posts) -> #render posts)

Promises/A+ v.s. JQuery

There are a many different promise libraries out like when.js, rsvp.js and Q to name a few.

JQuery, although definitely the most distributed promises library, does not conform to the most popular promises specification, Promises/A+.

As described by Domenic Denicola (author of the Q promise library and co-author of the Promises/A+ spec.) from the podcast Javascript Jabber:

[JQuery has] a fail flaw in their chaining implementation, which is that they don’t do thrown exception handling at all. So the whole abstraction breaks down when you can no longer throw an exception and have it turn into a rejected promise

For Example

d = $.Deferred()
d.then(function(){
throw new Error('err')
}).fail(function(){
console.log('fail')
})
d.resolve() // throws Error: err,
//instead of calling fail with the exception

This post is not about comparing the Promises/A+ and JQuery promises, but more trying to get you excited to use promises. If you read this post and want to see the differences, here is a document on the Q wiki about moving from JQuery promises to Q.

There are a few other differences, but this argument may become a moot point in the future with the announcemnet that ES6 will contain a native version of the Promises/A+ spec. Dominic Denicola helped spearhead this inclusion, which is described in his excellent presentation on working with standards bodies. This means browsers and Node.js may encourage (force?) libraries, like JQuery, to eventually use the standard promise implementation that will be provided.

What else?

I have not discussed many aspects of promises here, or provided an opinion on all matters of promises. All this post was written for was to encourage you to go and try them out. You could just open up a console and try them... right here.... right now. How easy is that!

References

Some references that might be useful:

O'Reilly Learning jQuery Deferreds: Taming Callback Hell with Deferreds and Promises

JQuery issue to make promises spec-compliantConFreaks vid from JQuery Conf (github)CommonJS Promises SpecsQ promisesJavascript Jabber: 037 Promises with Domenic Denicola and Kris KowalBack on Promise(github)

--

--