Tag Archives: es6

Promises, promises

For my inaugural post I’m going to talk a bit about promises in javascript. Promises are away to help you restructure asynchronous code so that program flow is more obvious. The usual way to write asynchronously in Javascript is to use callbacks, but that can lead to “callback hell” and the “pyramid of doom” – the ever rightward drift of your code due to nested callbacks

function loginFormHandler(username, password)
{
  login(username, password, function()
  {
    getUserDetails(username, function(userDetails)
    {
      getProfileImage(username, function(profileImage)
      {
        getPosts(username, function(posts)
        {
          showUserPage(userDetails, profileImage, posts);
        }, errorHandler);
      }, errorHandler);
    }, errorHandler);
  }, errorHandler);
}

You can alleviate this slightly by making all of your callbacks named functions, but then you lose the visual flow of the operations. With promises, instead of requiring a callback, you return a promise object. Each promise object has a method .then() that accepts success and error callbacks as parameters to be called when the asynchronous code finishes or fails.

But, and this is the important part, the .then() method returns another promise that will be fulfilled once the callback from the original promise have executed. This means you can chain promises to make asynchronous code that still shows the order that things will happen.

function loginFormHandler(username, password)
{
  login(username, password).
    then(getUserDetails).
    then(getProfileImage).
    then(getPosts).
    then(showUserPage, errorHandler);
}

When your asynchronous function has finished doing whatever it is doing you call promise.resolve(yourResult), while if it fails you can call promise.reject(anErrorObject). The appropriate callback is invoked with the argument passed to resolve() or reject(). The exception is when your callback itself returns another promise. Then the next .then() method along then chain will actually act on this new promise.

function getProfileImage(username)
{
  var promise = new Promise(); // The way of creating a new promise is library specific
  var image = new Image();
  image.addEventListener("load", function(event)
  {
    promise.resolve(this);
  });
  image.addEventListener("error", function(event)
  {
    promise.reject(new Error("Failed to load image"));
  });

  return promise;
}

Different promises libraries offer slightly different things, but the one that I have been playing with, called Q, lets errors propagate along the chain, so that if your first promise is rejected it will follow the .then() chain until it finds an error handler (or doesn’t find one at all which means it fails silently…)

Another nice feature is being able to group up an array of promises and wait for them all to complete, which in our example would allow us to fire off all four our sub functions in parallel since they don’t rely on each other, which in most cases would give you a speed boost.

Thoughts

Having used them in a real world project, I do have a few things I haven’t properly sorted out. I’ve found it hard to keep straight in my head what happens when return a promise from a callback. What happens if you resolve a promise with another promise? These aren’t difficult questions to get the answer to but they do mean stopping and thinking about it which can break your flow.

Whenever ES6 (the next version of the standard that Javascript is based on) is published, promises could become much cooler. There are examples written showing how, by using generators and yielding promises rather than returning them you can actually write code that looks very similar to synchronous code indeed. This isn’t going to be something you can use in cross-browser code for a long time though.

While a lot of my code got neater, some got uglier or more complicated in the promises version. I also tried explaining promises to a co-worker and not only failed to make it sound like an interesting solution to a problem, I actually made it sound like a massive over-complication. In part these things are probably due to only just getting to grips with the concepts – certainly the code got cleaner and cleaner with refactoring. Still, I’m keeping an open mind for the moment.

Advertisements