JavaScript’s default mode of operation is to rely heavily on callbacks: functions invoked when a longer operation (such as network I/O) finishes and delivers result. This makes it asynchronous, which is a notably different programming style than using blocking operations contained within threads (or their equivalents, like goroutines in Go).
Callbacks have numerous problems, though, out of which the most severe one is probably the phenomenon of “marching to the right”:
When using the (still) common style of providing a callback as the last argument to a function initiating an asynchronous operation, you get this annoying result of ever-increasing indentation as you chain those operations together. It feels like the language itself is telling you that it was not designed for such a complex stuff… Coincidence? ;)
But it gets worse. Operations may fail somewhere along the way, which is something you’d probably like to know about. Depending on the conventions your project, framework or environment uses, this could mean additional boilerplate inside the callbacks to distinguish success from error cases. This is typical in Node.js, where first argument of callback represents the error, if any:
Alternatively, you may be asked to provide the error handler separately; an “errback”, as it’s sometimes called. Splitting the code into small parts is great and everything, but here it means you’ll have two functions as arguments:
Giving them names and extracting somewhere outside may help readability a little, but will also prevent you from taking advantage of one of the JavaScript’s biggest benefits: superior support for anonymous functions and closures.
Thankfully, all this uphill struggle is not merely the language’s fault. What’s really flawed here is any API that blends data for long-running operations with callbacks that process their results. These two aspects should be kept separate, especially because the latter can be handled so much better.
Meet promises. Promise is an object that encapsulates the status of a time-consuming operation and allows callbacks to be attached to it. It may not sound very impressive (or even appear as unnecessary abstraction), but the advantages go way beyond simply making the code more clear.
Let’s start with that, though. There are few libraries implementing promises in JavaScript, and chances are that you’re already using one of them. Since quite some time now, jQuery has a very nice implementation of this concept, so if you happen to already include with your web application, I can wholeheartedly recommend having a look at it.
All examples here are using jQuery, but the ideas (or even API) are quite similar across the board.
Here’s a the first one, which should be both simple and useful:
Loading an image, especially over the network, is surely a task best done asynchronously. But there is no explicit callback
argument here, so how can we get the result once download completes?
No worries. This is exactly what the promise object is for, and we get it as a result from the above function. Rather than passing callback as the second argument, we can now attach it to the promise:
Similarly, we can specify an error handler for it:
or even both callback and “errback”, all while remaining neat and readable:
This is, in a nutshell, probably the simplest possible use of jQuery’s Promise. If you look into the loadImage
function, however, you will notice that we’re mostly operating on a different type of entity: the $.Deferred
object.
Why? $.Deferred
is the jQuery-specific Deferred object, which acts as a “controller” for promise. Unlike the jQuery.Promise
we return from our function, the Deferred object allows to change the state of the operation it “wraps”. It can be resolve
d – which indicates success – or it can be reject
ed – which signifies failure. We use these two methods in onload
and onerror
handlers of Image
, thus “translating” them into the done
/fail
callbacks on a Promise.
Moreover, Deferred also implements all the regular API of Promise. This is quite powerful feature that allows to mix additional logic into your asynchronous pipelines.
For example, let’s take an AJAX request; it may fail for network connectivity reasons, but you could also have your server respond with an error, and that would technically still be counted as success. We can fix that by intercepting the AJAX response early and reject
ing the Deferred object:
Callers of above function can chain more done
callbacks, but those will only be invoked if JSON returned in the response does not contain the "error"
key. Otherwise, the fail
ones will get called instead, by the virtue of Deferred object (here, jqXHR
) being reject
ed.
The last important advantage of promises I wanted to discuss here is their composability. What would be quite convoluted with vanilla callbacks, it’s very easy with promises. We can tie few Promise
objects together, creating a compound one that represents the joint state of all of them. This way it’s possible to, for example, launch a few AJAX requests concurrently and wait for them all to finish.
In jQuery, this can be achieved through the use of $.when
function. Simply pass a few existing Promises
to it and you will get a single one that encompasses them all:
The resulting Promise
behaves in rather predictable way: done
callbacks are invoked when all “inner” promises are resolved, while fail
gets called when any of them is rejected. For more fine grained control, you can also attach a then
callback; there you’ll be able to evaluate results of each individual promise.
Okay, but what if we don’t want to hardcode all these loadImage
calls, especially if we don’t even know how many of them we’ll have? This is not something supported specifically by jQuery, but JavaScript itself allows to call any function for arguments given as an array:
These examples should cover most of the use cases of promise objects. You may still want to use plain callbacks for the simplest of problems, but I’d recommend to at least have a try at doing it the, ahem, proper way :)
How about this?
Any resemblance to existing languages is purely coincidental.
Nice article ;)
This is a great promise object library https://github.com/kriskowal/q :)
@up: Looks good. As I understand, though, you may still need to use jQuery for AJAX if you don’t want to wrap XMLHTTPRequest yourself (and you probably don’t). For sharing code between client side and Node.js backend, Q looks quite superb, however.
@Tymur: Cute :) I think you could actually achieve something similar in JS if you add Underscore to the mix:
But personally, I’ll just… wouldn’t do that :)