Promises; in theory and reality

Preface

It's nearly 2015, do we really need another promises blog post? After having spoken to a few of my colleagues and heard reactions like this:

Oh God, kill me now.

I want to do my part in setting the record straight. This post is an adaptation of a talk that I gave at Uber. You can see the slides from that talk here.

First of all, let's take a quick look at why people have this kind of reaction to promises.

1. They're difficult to explain.

Promises have the same problems that Monads do. They're hard to explain properly, but that doesn't stop people from trying and doing it badly. The next time you hear somebody talking about Monads (shouldn't be hard in SF), ask them what a Monad is.

It's like this function, which is like a bunch of functions chained together into this structure. Except it's not really a function. It's good for pipelines or something. They're really cool.

Just stop it. Bad explanations only serve to put off the listener, resulting in more tedious eye-rolls. If you can't explain it, reference somebody that has already done it well.

2. They're over-hyped.

People raving about promises is almost as damaging as people not really understanding them. Everyone gets sick of hearing about them. If you aren't convinced after the first one, it's unlikely that another article droning on about how awesome promises are is going to sway you. Hopefully, it should be clear by now that this is not going to be another one of those articles.

3. They're sold as the answer to life, the universe and everything.

Drew Crawford, in his article Broken Promises, sums this up better than I ever could:

Why stop at lists! Promises in a box! Promises with a fox! Attributes are promises! Arrays are promises! Everything is a promise!

What are promises?

After that harsh, sweeping criticism in the preface, this isn't going to be easy. In fact, I'm going to absolve myself of that responsibility and pass you over to someone who can do a better job of this than me. There are plenty of articles out there devoted to this. I'm going to go ahead and skip to why I think promises are worth while.

Brilliant. Why should I give a shit?

OK, so you understand what promises are. What are some of the problems that they solve? That is not a small question, so I'm going to break it into two different categories:

Solving problems on the syntax level

The most obvious cases where promises can be useful are when they clearly present a better syntactical solution to a given problem. Let's take a look at a few common examples, bearing in mind that just because they're common, doesn't mean that they aren't fantastic.

'Callback Hell'

We've all seen this one. An oldie-but-goodie. Welcome to callback hell. I'm looking at you, node.

function serveDrink(order, callback){  
  findBottle(order, function(bottle){
    openBottle(bottle, function(){
      findGlass(function(glass){
        fillGlass(glass, bottle, function(drink){
          giveToCustomer(drink, function(){
            callback();
          });
        });
      });
    });
  });
}

When your async code uses promises, you can write this:

findBottle(order)  
.then(openBottle)
.then(findGlass)
.then(fillGlass)
.then(giveToCustomer); 

Angostura 1824 please. Delicious.

Simplify error handling

Error handling code can be painful, but any developer worth their weight in code should be writing it well. Unfortunately, JavaScript doesn't make the task very easy for us compared to some other languages. I find Node's convention of 'error first callbacks' one of the ugliest and most off-putting things about Node.

function serveDrink(order, callback){  
  findBottle(order, function(err, bottle){
    if(err) handleError(err);
    openBottle(bottle, function(err){
      if(err) handleError(err);
      ...
    });
  });
} 

With promises, you can avoid that monstrosity entirely:

findBottle(order)  
.then(openBottle)
.then(findGlass)
.then(fillGlass)
.then(giveToCustomer, handleError); 

If only the core libraries also used promises. Maybe I should be writing Dart.

Remove awkward parallelism

There are plenty of places, both in the browser and on the server, that we want to parallelise tasks. Almost all of the time, we also want to know when all of those tasks complete. Without promises, we're forced to construct some method to accomplish this:

var count = 0;  
var onFinished = function(finishedFunc){  
  count++;
  if(count == 2){
    finishedFunc();
  };
};

var order1 = serveDrink('rum', onFinished);  
var order2 = serveDrink('vodka', onFinished);  

This is a pattern that I see all over the place. Sure, there are ways to improve it, be it with utility functions (like Underscore's _.after) or using some kind of task queue, but I feel like neither are as clear as an implementation with promises:

var order1 = serveDrink('rum');  
var order2 = serveDrink('vodka');  
Promise.all([order1, order2]).then(finishedFunc);  
Solving problems on the semantic level

I often read that promises were 'invented' to solve these problems. Maybe that's somewhat true for the parallelism example, but thinking of Promises as tools just to provide nicer syntax is missing the fundamental point of them. These benefits are essentially side-effects of modeling programming problems in a semantically different way.

The nature of promises is that they remain immune to changing circumstances. Frank Underwood, House of Cards - 2013

Maintain encapsulation

Promises maintain state where callbacks do not - a huge benefit for consumers of a function which returns a promise. Let's take a simple example where we want to log the output of our API calls to the console. Using callbacks for flow control, I either have to inject the call to the logger inside by callback, or I have to call the logger in the API module. Either way, my API call now knows about logging behaviour.

Logger.prototype.logResult = function(promise){  
  return promise.then(function(result){
    console.log(result);
  }
} 

When promises are passed around, we don't need to worry about mixing these concerns. We can simply define a function which takes a promise, logs its result and passes another promise on to the next function in the chain. It should be evident that the real power of this pattern comes in to play when that logging function is also async.

Composition in an asynchronous world

In a chain of synchronous calls, functional composition is easy. In an async world, things become a lot more difficult - you can't return values because they just aren't there yet. Likewise, you can't throw exceptions because nothing is there to handle them.

Promises bring that ability to asynchronous calls. They give us back functional composition and error bubbling. Without Promises, we're left using callbacks for flow control and that leads me to me last point on this topic:

Functions for functions, not flow control

Whilst I understand that a lot of people are more comfortable with an imperative programming style, I have come to adopt - and love - a much more functional approach over my last few years as an engineer.

As a controversial aside, I find it brilliantly ironic that the node community prides itsself on the Unix philosopy of 'doing one thing, and doing it well', yet the standard node convetion is based around callbacks - an intrinsically coupled approach to flow control. As James Coglan points out, Promises are Node's biggest missed opportunity.

That leads me to what I feel is really the biggest win for Promises. Now that I can use Promise to abstract the flow control away from my functions, I'm left with the ability to write functions that do one thing well and can be composed and manipulated like their mathematical namesake.

Promises in practice

What the fuck was I talking about in that last section?

I understand that a lot of this is fairly difficult to describe/comprehend without a plethora of examples. When giving this as a talk, I spent a long time choosing my words carefully, but nothing was as helpful as solid examples.

Let's take a look at a quick demo of functional composistion using promises. As I previously stated, functional composition in a synchronouse world is easy. Imagine you have a list of numbers, and you would like to create function which filters that list into another list of numbers. With a little bit of underscore magic, we can do that very easily and extensibly:

var numbers = [1,2,3,4,5,6,7,8,9,10,11,12];

var findDivisibleBy = function(collection, value){  
  return _.filter(collection, function(n){
    return n % value == 0;
  });
};

var findEven = _.partial(findDivisibleBy, _, 2);  
var findBy3 = _.partial(findDivisibleBy, _, 3);  
var filterList = _.compose(findBy3, findEven);

filterList(numbers); // [6,12]

Let's take a look at the same problem, but where the filtering is done in an asynchronous manner with callbacks. For the purposes of demonstration, let's modify our filter function to take a second before completing:

var findDivisibleBy = function(collection, value, callback){  
  setTimeout(function(){
    var filteredList = _.filter(collection, function(num){
      return num % value == 0;
    });
    callback(filteredList);
  }, 1000);
};

var findEven = function(collection, callback){  
  findDivisibleBy(collection, 2, function(result){
    callback(result);
  });
};

var findBy3 = function(collection, callback){  
  findDivisibleBy(collection, 3, function(result){
    callback(result);
  });
};

findEven(numbers, function(result){  
  findBy3(result, function(filtered){
    console.log(filtered); // [6,12]  
  });
});

When I said that Promises bring functional composition to an async world, I meant it. Here's what a simple rewrite can do, using jQuery's (broken) Promise implementation

var findDivisibleBy = function(collection, value){  
  var deferred = $.Deferred();
  setTimeout(function(){
    var filteredList = _.filter(collection, function(num){
      return num % value == 0;
    });
    deferred.resolveWith(null, [filteredList]);
  }, 1000);
  return deferred.promise();
};

var findEven = _.partial(findDivisibleBy, _, 2);  
var findBy3 = _.partial(findDivisibleBy, _, 3);  
var findBy4 = _.partial(findDivisibleBy, _, 4);

findEven(numbers)  
.then(findBy3)
.then(findBy4) // [6,12]

By having findDivisbleBy return a promise, we're free to pass that function around without having to worry about how the resulting value is processed.

Flow control

This next example comes from a real world sitation that I encountered when writing some of the front end code for Uber for Business. In the app, we want to show the total number of 'pending' employees, 'active' employees and 'all' employees. As everything is paged server side, we need to query the backend for the number of pending and active employees, but there's no need to waste another round trip to ask for the total number of employees; we simply sum the results of the previous two queries.

With a callback based implementation, we see how the encapusltion of each call leaks into the other one:

var results = {};

// Function to call when we're done/
var whenDone = function(results){  
  console.log('Finished!');
  console.log(_.pairs(results));
}

// Get the count for a given segment of users
var getCount = function(key, callback){  
  setTimeout(function(){
    callback(_.random(1, 10));
  }, 2000);
}

getCount('active', function(value){  
  results.active = value;
  if(results.active && results.pending){
    results.all = results.active + results.pending;
    whenDone(results);
  }
});

getCount('pending', function(value){  
  results.pending = value;
  if(results.active && results.pending){
    results.all = results.active + results.pending;
    whenDone(results);
  }
}); 

Because we only want to call our sum code when both calls are complete, we've broken the encapsulation of our callback for getCount(active) by leaking knowledge of the pending call and vis verca.

We could rewrite this to be a little better by wrapping our function for calculating all in an _.after(2).times(...) utility, but there's a much more semantic and extensible way of solving this problem. Simply rewriting the function to return a promise let's us register handlers on each individual call without disrupting the the rest of the flow:

var getCount = function(key){  
  var deferred = new $.Deferred();
  setTimeout(function(){
    deferred.resolveWith(this, [_.random(1, 10)]);
  }, 2000);
  return deferred.promise();
};

var getActive = getCount('active');  
var getPending = getCount('pending');

getActive.done(function(result){  
  results['active'] = result;
});

getPending.done(function(result){  
  results['pending'] = result;
});

$.when(getActive, getPending).done(function(activeResult, pendingResult){
  results['all'] = activeResult + pendingResult;
  whenDone(results);
});

We now have a much cleaner solution. Pending doesn't know about active, active doesn't know about pending. Extending this to add a 3rd segment of users is now trivial rather than requiring a total rewrite.

In Conclusion

Promises are not the answer to Life the Universe and Everything, but they are definitely a helpful tool if you want to write semantic and functional code.

The biggest win for me when writing a lot of the Uber for Business front end was having a general semantic way to use promises to handle HTTP requests. For that, check out my HTTPDeferred library and associated blog post.