ES6 Promises are here!

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:

promises1

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:

promises2

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:

promises3

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:

promises4

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:

  1. The second then() callback will only get invoked if both of the fetches succeed
  2. 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:

  1. The latest versions of Chrome, Firefox and Safari all support ES6 promises natively
  2. There are a number of polyfills available for ES6 promises so that you can use them in browsers that don’t support them natively
  3. 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.

ben.teese@shinesolutions.com

I'm a Senior Consultant at Shine Solutions.

No Comments

Leave a Reply