The ES6 spaceship has landed and promises are amongst the first new features to come down the ramp. I’m very excited about this.
This is because I love promises. They’ve been lurking around for years in different incarnations, but now that we’ve settled on a standard, I think now’s the time to learn them if you haven’t already.
So in this post I’m going to talk about what promises are, why they’re awesome, and how you can start using them right now.
Async is Hard
This makes it hard to do error handling properly. It makes it hard to unit test. And it makes it hard to write code that isn’t a giant mess of nested callbacks.
The good news is that promises are here to help get us out of callback hell and back to a better place. Let’s see how.
What’s A Promise?
A promise is an object that encapsulates the result of an asynchronous operation. The key part is that the asynchronous operation mightn’t have completed yet.
If that sounds a little abstract to you, the best way to understand promises is to see them in action. Let’s start with some code that doesn’t use promises: a good old fashioned jQuery $.ajax() call that gets some stuff from a backend:
You can see that we pass callback functions to handle both the error and success case. The key thing is that those callbacks are passed to the
$.ajax function directly.
Here’s how we’d do the same thing with promises:
The first thing you might be wondering is what that
fetch() function is. Well that’s the new
fetch() API, a next-generation mechanism for making HTTP calls. It irons out many of the rough edges of XmlHttpRequest and, most importantly, it returns ES6 Promises. Support for
fetch() landed in Chrome recently, and there are polyfills available for everywhere else.
You don’t have to use the
fetch() API if you don’t want – you could wrap
XmlHttpRequest with promises yourself. However, for the purposes of this demo, we’re going to use
Anyway, the first thing we do with the promise that we get back from
fetch() is call a
then() method on it to register a function to be invoked on success. We then also call
catch() to register a function to be invoked if an error occurs.
You’re probably wondering how this is a great improvement over doing it the old-fashioned way. Well, promises only really come into their own when you start to nest asynchronous operations.
Consider the case where we want to first get
/stuff, and then if that is successful, get
We’ve had to nest the second
$.ajax call within the first. This isn’t too bad, except that we’ve also had to nest another
error callback. This is repetitive and, whilst we can eliminate some of the duplication, there’ll always be some doubling-up. Furthermore, as we compose more and more asynchronous operations, it’s easy to accidentally forget an error handler and, in doing so, have our program swallow errors without us realising it.
Here’s how we’d approach the same situation with promises:
We nest a second call to
fetch() within the first
then() callback. Note how we also
return the promise that is returned by the second
We then call
then() again with a success callback, and call
catch() with a failure callback.
The crucial thing to understand about this is that:
- The second
then()callback will only get invoked if both of the fetches succeed
catch()callback will get invoked if either of the fetches fail
This is called promise chaining and it is the thing I love most about promises. We’ve been able to flatten out our callback tree, and avoid duplicating our error handling logic.
How Can I Use Promises Now?
If you want to use ES6 Promises now, you’ve got a few options:
- The latest versions of Chrome, Firefox and Safari all support ES6 promises natively
- There are a number of polyfills available for ES6 promises so that you can use them in browsers that don’t support them natively
- If you’re feeling more adventurous, you can use an ES6-to-ES5 transpiler like Babel or Traceur
If you’re not able to do any of these things, it’s also worth noting that a number of JS frameworks have been providing their own promises implementations for some time now. Angular’s
$q service is excellent, as is the Bluebird library that is commonly used with Node. Even jQuery does it – the
jqXHR object returned by
$.ajax() is a promise (although it’s a bit different to an ES6 promise).
ES6 Promises Are Here To Help
For example, by combining them with ES6 Generators and simple wrapper libraries, you can start writing coroutines with no callbacks at all. And in ES7 we’ll be able to ditch the wrapper libraries entirely and just write full-blown async functions out-of-the-box.
The bottom line is that promises are here to stay, and beat the heck out of writing nested callbacks yourself. If you haven’t dived into promises yet, now’s the time to do it.