Press enter to see results or esc to cancel.

Node.js Async: How to Use ES6 Promises

Introduction

Promises are an alternative to callbacks for delivering the results of an asynchronous computation. They require more effort from implementors of asynchronous functions, but provide several benefits for users of those functions.

Promises are a pattern that helps with one particular kind of asynchronous programming: a function (or method) that returns a single result asynchronously. One popular way of receiving such a result is via a callback (“callbacks as continuations”):

Promises provide a better way of working with callbacks: Now an asynchronous function returns a Promise, an object that serves as a placeholder and container for the final result. Callbacks registered via the Promise method then() are notified of the result:

Compared to callbacks as continuations, Promises have the following advantages:

  • No inversion of control: similarly to synchronous code, Promise-based functions return results, they don’t (directly) continue – and control – execution via callbacks. That is, the caller stays in control.
  • Chaining is simpler: If the callback of then() returns a Promise (e.g. the result of calling another Promise-based function) then then() returns that Promise (how this really works is more complicated and explained later). As a consequence, you can chain then() method calls:
  • Composing asynchronous calls (loops, mapping, etc.): is a little easier, because you have data (Promise objects) you can work with.
  • Error handling: As we shall see later, error handling is simpler with Promises, because, once again, there isn’t an inversion of control. Furthermore, both exceptions and asynchronous errors are managed the same way.
  • Cleaner signatures: With callbacks, the parameters of a function are mixed; some are input for the function, others are responsible for delivering its output. With Promises, function signatures become cleaner; all parameters are input.
  • Standardized: Prior to Promises, there were several incompatible ways of handling asynchronous results (Node.js callbacks, XMLHttpRequest, IndexedDB, etc.). With Promises, there is a clearly defined standard: ECMAScript 6. ES6 follows the standard Promises/A+ [1]. Since ES6, an increasing number of APIs is based on Promises.

A Promise is an event emitter

Registering the event listener (line B) can be done after calling asyncFunc(), because the callback handed to setTimeout() (line A) is executed asynchronously (after this piece of code is finished).

Normal event emitters specialize in delivering multiple events, starting as soon as you register.

In contrast, Promises specialize in delivering exactly one value and come with built-in protection against registering too late: the result of a Promise is cached and passed to event listeners that are registered after the Promise was settled.

The states of Promises

Once a result was delivered via a Promise, the Promise stays locked in to that result. That means each Promise is always in either one of three (mutually exclusive) states:

  • Pending: the result hasn’t been computed, yet (the initial state of each Promise)
  • Fulfilled: the result was computed successfully
  • Rejected: a failure occurred during computation

A Promise is settled (the computation it represents has finished) if it is either fulfilled or rejected. A Promise can only be settled once and then stays settled. Subsequent attempts to settle have no effect.

 

promises-states

 

 

 

 

 

 

 

The parameter of new Promise() (starting in line A) is called an executor:

  • Resolving: If the computation went well, the executor sends the result via resolve(). That usually fulfills the Promise p. But it may not – resolving with a Promise q leads to p tracking q: If q is still pending then so is p. However q is settled, p will be settled the same way.
  • Rejecting: If an error happened, the executor notifies the Promise consumer via reject(). That always rejects the Promise.

If an exception is thrown inside the executor, p is rejected with that exception.

Promises are always asynchronous

A Promise library has complete control over whether results are delivered to Promise reactions synchronously (right away) or asynchronously (after the current continuation, the current piece of code, is finished). However, the Promises/A+ specification demands that the latter mode of execution be always used. It states so via the following requirement (2.2.4) for the then() method:

onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

That means that your code can rely on run-to-completion semantics (as explained in the previous chapter) and that chaining Promises won’t starve other tasks of processing time.

Additionally, this constraint prevents you from writing functions that sometimes return results immediately, sometimes asynchronously. This is an anti-pattern, because it makes code unpredictable. For more information, consult “Designing APIs for Asynchrony” by Isaac Z. Schlueter.

 

Basics

A promise is just an object.
A promise can be either pending or settled.
A settled promise can be fulfilled or rejected.
We can ‘listen’ for a certain promise to be settled with the .then() and .catch().

With a single then() you can receive two callback functions.
One for onfulfilled success and one for onfailure.

Then

A then can only placed after promise.
Thens can be chained.

The return value of the previous then will be the argument for the next then in the chain.

If that return value is a promise the next then in the chain only gets called if that promise is fulfilled.

If the then returns nothing the next then argument will be undefined.

Things you can return from a then:

  • You can simply return a value from a then.
  • You can return a promise.
  • You can return a function that returns a promise.
  • If you use return synchronously it does not get called again.

Its important to note that in a promise chain if you have an async function call which returns a promise you must return it inside the then() otherwise the chain goes on without waiting for the promise.

Catch in chains

You can throw synch error.
You can reject a promise.
You can directly use Promise.resolve() and Promise.reject().
You can listen to error from a previous then with a second argument from then.
You can listen to a whole chain with catch.
You can add a then after a catch.

 

Branching chains

You can place a then inside a then or event in a catch as long it has an async call before it.

A child branch can have its own thens and catches.
Returning from the last then in a branch returns the parents branch next then.

Throwing an error in a child branch triggers the child branch catch.
If it does not have a catch it lands in the parents nearest catch and so on.

If you return from a child 2 levels deep and there are no immidiate parent then it will return the value for the nearest parent then aka. first parent then.

Here we have multiple .then branches.
At the end of the first branch we throw an error to the parent branch catch.
The second .then branch returns a value to the parent branch.

Dealing with callback interface

All of the official node.js modules and most of the npm packages use the callback interface.

This can make it tricky to use them with promises.

Placing directly inside a then

This is ideal if you need a callback based interface at the end of the chain.

The problem with this is that if you place another then after the callback it will fire before the actual async functions has been finished.

Wrapping in a promise

By wrapping in a promise a callback function can be used in a promise chain easily.
I wrap most of my functions in a similar way.

Promisify

You can promisfy an existing callback based module with Promisify. This way you can promisify whole modules without having to modify it.

Promise.all

Good for waiting for all the promises to fulfill at once.

Promise.race

Waiting for multiple promises but only get the one which resolves faster.

 

Iterating over arrays with promises

This can be tricky. You can use custom Promise libraries to make it easier but you can check out my tutorial on Sequential iteration pattern for an easy solution that works most of the time.

 

Refers:

  • http://exploringjs.com/es6/ch_promises.html
  • http://peterforgacs.github.io/2017/04/30/Node-js-Async-Promises/#Catch-in-chains

 

Tags

Comments

Leave a Comment