Discussion last night at the Opera/Mozilla/Left Logic/Kryogenix Christmas Strategy Summit about JavaScript Promises1, where by “discussion” I mean Remy tried to explain it to Bruce and Chris with me chipping in “helpfully” during the discussion. Anyway, Bruce has tried to con me into saying that I promised2 an explanation.
I am not going to explain exactly what promises are, other than to say: they’re a way of making your asynchronous code look like it’s synchronous, because async code is hard to write, hard to understand, and hard to debug3. The important point to take away is: you can start using promises now, and hardly anything will change in your code. So you should do that.
Asynchronously doing a thing
Let’s say you want to load an image, and then when that image is loaded, update the page to say it was loaded. Pretty trivial code:
function loadImage(imgsrc, callback) {
var i = new Image();
i.onload = callback;
i.src = imgsrc;
}
loadImage("http://kryogenix.org/images/me/bigbwme.jpg", function() {
document.querySelector("p").innerHTML = "The image is loaded";
});
We create a little helper function which loads the image and then calls our callback; in our callback, we update the text of the page.
The Promises version
function loadImage(imgsrc) {
var deferred = Q.defer(); // using the Q promises library; see below
var i = new Image();
i.onload = function() { deferred.resolve(); }
i.src = imgsrc;
return deferred.promise;
}
var promise = loadImage("http://kryogenix.org/images/me/bigbwme.jpg");
promise.then(function() {
document.querySelector("p").innerHTML = "The image is loaded";
});
Promises are a tiny little bit more complicated, but not much. Instead
of having the helper function call our callback directly, the helper
function returns a Promise (straight away, synchronously) and the
promise’s then
function calls our callback. There are a few little
wrinkles in there, like what resolve
means (in this case, it basically
means: run the promise’s then
function4), but if you’ve written any JS, you can
probably read that code and understand roughly what it’s doing. You
could start doing that today, right?5 Just mechanically start returning promises and using their then
functions instead of using callbacks. You don’t really need to grasp the
complexity underlying all this stuff. (Note: there are about a zillion
Promises implementations6.
I’m using here (and generally use) Kris Kowal’s Q, and that’s why
the code says Q.defer
, but if you prefer another one, go ahead.)
But there’s an obvious question: why should I bother to use these promise things? Well, here’s a second example. What we want to do now is load two images, and then update the page when both of them are loaded.
A second example, with callbacks
function loadImage(imgsrc, callback) {
var i = new Image();
i.onload = callback;
i.src = imgsrc;
}
var image1loaded = false, image2loaded = false;
function showTheyAreLoaded() {
document.querySelector("p").innerHTML = "The images are loaded";
}
loadImage("http://kryogenix.org/images/me/bigbwme.jpg", function() {
image1loaded = true;
if (image1loaded && image2loaded) showTheyAreLoaded();
});
loadImage("http://kryogenix.org/images/me/hackergotchi-simpler.png", function() {
image2loaded = true;
if (image1loaded && image2loaded) showTheyAreLoaded();
});
You will notice here that our loadImage
function hasn’t changed from
the previous async version. What we do now is we have one flag per image
(image1loaded
and image2loaded
); when a specific image loads, we set
its flag, and then check if all the flags are set. If they are all set,
then we update the page.
That second example, with promises
function loadImage(imgsrc) {
var deferred = Q.defer();
var i = new Image();
i.onload = function() { deferred.resolve(); }
i.src = imgsrc;
return deferred.promise;
}
var promise1 = loadImage("http://kryogenix.org/images/me/bigbwme.jpg");
var promise2 = loadImage("http://kryogenix.org/images/me/hackergotchi-simpler.png");
Q.all([promise1, promise2]).then(function() {
document.querySelector("p").innerHTML = "The images are loaded";
});
Again, our loadImage
function hasn’t changed from the previous
promises version. Now, though, because we’re using promises, we have
this rather handy all
function; you pass it a list of promises, and
then the all
function fires all
‘s own then
callback when all the
promises are done.
The two item version isn’t that complicated, but you can imagine that it’d be quite a lot more annoying to do the async version with three or four or fifty images7; the promises version hardly gets any more difficult at all.
You can’t handle the truth
You see, that’s it, for promises. If you start using them today, almost
everything you do with them will be like the very first example: instead
of passing a callback function around, you’ll pass around a promise and
do things in its then
function instead. And that’s all. Your life is
no more complicated. And when occasionally you need something like
all
, it’ll be there for you and you can use it trivially.
Promises are capable of a lot, lot, lot more than this. However, what
this means is that everyone talking about them also tries to explain all
the complicated whizzy stuff you can do with them too, and how they
work, and how clever the implementation is. Don’t listen. If you decide
to become a hardcore person and write massive complex things, then sure
it’s useful. But in practice most developers, most of the time, can just
use a promise instead of a callback, and then occasionally use either
all
or any
when you hit those “but I need to do two things at once
and wait for both” situations. Do not be put off by the complexity. You
don’t care about the complexity, and you probably don’t need it. Just
start using them now, and the hard stuff will wait patiently for you to
get to it.
- warning: official spec! also dense, difficult terminology! ↩
- ha! ba-dump tish ↩
- See Shot of Jaq podcast episode “Going Async” for more on this from 2010; my opinion has not changed ↩
- yes, if you know about this stuff then that’s not what it means. Also, I have ignored error checking in all examples ↩
- obviously this doesn’t just apply to image loading, but to anything async; you could XHR with function xhr(url) { var x = new XMLHttpRequest(), d=Q.defer(); x.open(“GET”, url); x.onreadystatechange=function() { if (x.readyState==4) d.resolve(x.responseText); } x.send(); return d.promise; } ↩
- and actual native browser support with no library at all is coming. See MDN on Promises for details - mdn. ↩
- yes, of course you’d write a little helper which puts all the loaded image flags into a dict keyed on the img src, and ticks them all off when they arrive. Congratulations. You’ve just written the first 4% of your own promises library. Wouldn’t it have been easier to just use someone else’s so that you can get on with real work? ↩