Promises, promises

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";
    });

live example in jsbin

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";
    });

live example in jsbin

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();
    });

live example in jsbin

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";
    });

live example in jsbin

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 alls 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.

  1. warning: official spec! also dense, difficult terminology!
  2. ha! ba-dump tish
  3. See Shot of Jaq podcast episode “Going Async” for more on this from 2010; my opinion has not changed
  4. yes, if you know about this stuff then that’s not what it means. Also, I have ignored error checking in all examples
  5. 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; }
  6. and actual native browser support with no library at all is coming. See MDN on Promises for details - mdn.
  7. 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?
I'm currently available for hire, to help you plan, architect, and build new systems, and for technical writing and articles. You can take a look at some projects I've worked on and some of my writing. If you'd like to talk about your upcoming project, do get in touch.

More in the discussion (powered by webmentions)

  • (no mentions, yet.)