Thursday, 13 September, 2018 UTC

Summary

I recently refactored Netflix Party to use promises for asynchronous tasks. I’d heard about promises before, but this was the first time I really used them. I’m really satisfied with how much cleaner the code became!

What’s a promise?

To be concise, promises are composable wrappers for callbacks. They’re useful for sequencing synchronous and asynchronous actions.JavaScript promises are similar in API to RSVP.js. Promises let you start with code like this:

Maybe it’s not clear why the bottom version is better—it’s because there’s no nesting. Imagine if the example were much larger. The top version might have a dozen nested callbacks, which would be hard to read and hard to rearrange. The bottom version would be a straightforward sequence of actions, not very different from how the code would look if it were synchronous.

A promise supports two methods: thenand catch.then takes a continuation, which is just a callback that takes the result as an argument and returns a new promise or any other value. Similarly, catch is the callback that is invoked when the action raises an exception or fails in some other way.

The full version of then encompasses both behaviors:

promise.then(onFulfilled, onRejected)

So catch can be thought of as being defined as follows:

promise.catch(onRejected) := promise.then(null, onRejected)

Note: It doesn’t matter if the callbacks are attached before or after the promise has been fulfilled. If the callback is attached after the promise is fulfilled, it will still be called.

How do you make a promise?

You don’t have to build an object with then and catch methods to make a promise. You should use the Promise constructor instead:

You just have to call the resolve callback when your promise is fulfilled, or call reject if something goes wrong. Equivalently, you can just raise an exception.

If you want to wrap a value in a promise that resolves immediately, you can simply write Promise.resolve(value). Or if you want to make a promise that fails immediately, you can write Promise.reject(error).

Timeouts

The setTimeout function is used to execute some code after a specified delay. I’ve found it’s really useful to define a promise version of it:

Is that surprising to you? It was a little surprising to me. The then thunk is deferred, but the one passed to the Promise constructor isn’t.

But we can take advantage of the behavior of then. I often want to defer the execution of some piece of code until after the current synchronous code is finished. I used to use setTimeout(func, 1) to do this. But now you can make a promise for it:

var defer = Promise.resolve();

You can use it like this:

defer.then(function() {
console.log('A');
});
console.log('B');

…which prints B, then A. Although this isn’t any shorter than just using setTimeout(func, 1), it clarifies the intention and is composable with other promises. The fact that it can be chained with other promises leads to better structured code.

Closing remarks

The true value of promises is realized when all asynchronous code is structured with them. By making composable promises for things like timers and AJAX actions, it’s easy to avoid nesting and write more linear code.

A few things were surprising to me:

The callbacks for then and catch can return any value, but they behave differently if the value is a promise. Ordinarily, the return value is passed to the next then continuation in the chain. But if the return value is a promise, the promise’s fulfillment value is passed to the continuation instead. This means returning Promise.resolve(x) is the same as returning x.

The function passed to the Promise constructor is executed synchronously, but any continuations sequenced with then or catch will be deferred until the next event loop cycle. The latter behavior is why defer works.

catch is a reserved keyword used for exception handling in JavaScript, but it’s also the name of the method to attach an error handler to a promise. It seems like an unfortunate naming collision. On the bright side, it’s easy to remember; so maybe the collision isn’t unfortunate after all!

Surprise #1 is weird to me because of the following thought experiment: what if you actually want to pass a promise to the next callback in the chain? If you try to do so naively, the promise will be automatically unwrapped and you won’t get the expected result. One might ask: why would anyone want to pass a promise to the callback? Well, because maybe the code doesn’t know what kind of value it’s passing to the callback. Maybe the value is produced elsewhere in the program, and it’s supposed to just be threaded through. Now you have to first check if it’s a promise, and if so wrap it in some kind of other thing to prevent it from being unfolded in transit. So I would prefer that the callbacks always have to return a promise (even if it’s just a value wrapped in Promise.resolve(value)).

Having thought about surprise #2 for a moment, I’ve come to the conclusion that the callbacks have to be deferred. Here’s why: suppose a promise fails. It needs to call the next error handler in the chain, or throw an exception if there isn’t one. But it has to wait for the error handlers to be attached in the first place. So how long does it wait? The most natural answer is that it waits until the next iteration of the event loop.

Despite these oddities, promises are a welcome addition to JavaScript. Callback hell was one of my least favorite parts of writing JavaScript, but now it’s a non-issue.

See more:

Programming virgin with Javascript

15 New JavaScript Libraries for Developers

The post Fun With Promises in JavaScript appeared first on I'm Programmer.