Which async control flow library to choose?

There are quite a few async control flow libraries out there. Some of them are older and well established, some of them take an alternate approach such as streams. Trying to decide one over another feels overwhelming. Choosing one library now, will you be using an unmaintained project in 12 months?

"Which async control flow library to choose?"

Let's assume you do not want to use callbacks directly. They are a valid option, and embraced by many, but they can create a hard to follow code paths. You want something more readable. Let's take a look at what's on the horizon for asynchronous libraries.

Go with the flow - for support and longevity

There are 270 000 published npm modules today. Are they all maintained and top notch quality? Not likely. The ease of starting a new GitHub repository and publishing to npm makes it easy to start something and not see the project through. There are design principles to create small modules that perform one task well, but there's still a chance of a module getting abandoned. Life happens to everyone.

One way to approach this problem is to bet on a project that already has a large user base. Choosing a popular project has multiple benefits. There will be users with similar issues that you have; there will be help available online, and there will be professional educational material in the forms of books and courses. A widely used project will have multiple people vested in its success. There will be bug fixes and pull requests. There will be people that step up as maintainers. A popular project is not going to get abandoned as easily as a marginal project would.

The problem with JavaScript ecosystem is that what's popular today may not be popular tomorrow. That's why you need to keep your eyes open and feel what direction the consensus is moving. With async control flow there is a pattern emerging.

Promises are in the ES6 specification

The JavaScript language received a big shakedown when the ECMAScript 2015 edition (ES6) was released. It was a major improvement to the language and contained many significant updates. Not the smallest update is the inclusion of Promises.

The ES6 spec contains Promises as first class citizens. It's a small subset of the features of what could be done with Promises. But the fact that the core functionality is now cemented at the specification level is significant.

Node has a working group to convert Core API into Promises

Node has a working group dedicated to converting the Core API to Promises. The working group's goal is to retain callbacks but also offer support for Promise users. You can follow its progress at Promises Working Group Repository.

One suggested way of introducing Promises into the Core API is to have two versions of the API. The regular one that takes callbacks and another one where the asynchronous functions return Promises. Current suggestion offers two ways to load the Promises-returning API: either as module property require('fs').promised or as alternate core module name require('promise/fs'). The usage would look like the following.

1 var fs = require('fs').promised;
2 fs.readFile('data.txt', 'utf8').then((data) => {
3     console.log(data);

4 }).catch({
5     // handle errors
6 });

This is another clue that the future might look friendly for Promises. As soon as this working group reaches it's goal it's hard to avoid using Promises - they would be available at the core level.

Generators allow yielding Promises

Creating complex asynchronous code has always led nested code. Even with Promises, you need to nest at least one level to get on the .then-chain. Promises have another drawback: referring to anything other than the previous Promise result is laborious.

Generators, also in the ES6 spec, allow us to create a special function that can be used to write clean non-nested asynchronous code using the yield keyword. The special function has many names, including spawn, co , coroutine and there are existing implementations for it. Here's what it would look like when using coroutine from Bluebird.

1 var fs = require('fs-promise');
2 var bluebird = require('bluebird');
4 bluebird.coroutine(function* () {
5     var file1 = yield fs.readFile('file1.txt', 'utf8'
6     var file2 = yield fs.readFile('file2.txt', 'utf8');
8     console.log(`total length: ${file1.length + file2.length}`);
9 })();

Upcoming async/await allows awaiting Promises

The upcoming ES7 brings us the long-awaited async/await keywords. It allows us to write clean asynchronous code without having to use a third party coroutine implementation. We can directly await Promise to be fulfilled inside an async function. The same example would look like the following.

1 var fs = require('fs-promise');
3 (async function() {
4     var file1 = await fs.readFile('file1.txt', 'utf8');
5     var file2 = await fs.readFile('file2.txt', 'utf8');
7     console.log(`total length: ${file1.length + file2.length}`);
8 })();

Future looks Promising

To be safe, to be supported, to ensure longevity - ditch everything else and pick a promises based library. You can use the native implementation for smaller projects or you can use a library based Promises with supporting utilities for a larger project. It can be enhanced with Promise-supporting coroutines to avoid nesting promises. This way you'll also be set when async/await arrives.

Investing in Promises looks to be a great choice.

Related articles


There are 350 000 NPM packages. Your task: pick one.

Learn what to look for in a reliable NPM package.

Get Guide

Share article: