08 May 2015 ES6 Promises are here!
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
If you’d told me five year’s ago that I’d end up spending most of my time coding Javascript, I’d have laughed at you. But here we are now, writing more and more Javascript, both for the client and server.
Javascript has it’s quirks, but we can work around most of them. However, in my experience the biggest challenge of Javascript isn’t a bug, but a core characteristic of the way that Javascript runtime environments work. In short, they are asynchronous.
Writing asynchronous Javascript is hard to do right, because the default mechanism for doing so is to pass around callback functions.
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 fetch()
.
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.
So What?
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 /moreStuff
:
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 success
and 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 fetch()
.
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 - The
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
Promises are an awesome way to manage asynchronous code and, with their arrival in ES6, they’re going to become the standard way to do it. Furthermore, ES6 promises will act as the basic building blocks for more advanced mechanisms for writing asynchronous Javascript that are coming down the pipeline.
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.
No Comments