Asynchronous Control Flow Patterns with ES2015 and beyond

In this article,by Luciano Mammino, the author of the book Node.js Design Patterns, Second Edition, we will explore async await, an innovative syntaxthat will be available in JavaScript as part of the release of ECMAScript 2017.

(For more resources related to this topic, see here.)

Async await using Babel

Callbacks, promises, and generators turn out to be the weapons at our disposal to deal with asynchronous code in JavaScript and in Node.js. As we have seen, generators are very interesting because they offer a way to actually suspend the execution of a function and resume it at a later stage. Now we can adopt this feature to write asynchronous codethatallowsdevelopers to write functions that "appear" to block at each asynchronous operation, waiting for the results before continuing with the following statement.

The problem is that generator functions are designed to deal mostly with iterators and their usage with asynchronous code feels a bit cumbersome.It might be hard to understand,leading to code that is hard to read and maintain.

But there is hope that there will be a cleaner syntax sometime in the near future. In fact, there is an interesting proposal that will be introduced with the ECMAScript 2017 specification that defines the async function's syntax.

You can read more about the current status of the async await proposal at https://tc39.github.io/ecmascript-asyncawait/.

The async function specification aims to dramatically improve the language-level model for writing asynchronous code by introducing two new keywords into the language: async and await.

To clarify how these keywords are meant to be used and why they are useful, let's see a very quick example:

const request = require('request');

function getPageHtml(url) {
return new Promise(function(resolve, reject) {
    request(url, function(error, response, body) {
      resolve(body);
    });
  });
}

async function main() {
const html = awaitgetPageHtml('http://google.com');
console.log(html);
}

main();
console.log('Loading...');

In this code,there are two functions: getPageHtml and main. The first one is a very simple function that fetches the HTML code of a remote web page given its URL. It's worth noticing that this function returns a promise.

The main function is the most interesting one because it's where the new async and await keywords are used. The first thing to notice is that the function is prefixed with the async keyword. This means that the function executes asynchronous code and allows it to use the await keyword within its body. The await keyword before the call to getPageHtml tells the JavaScript interpreter to "await" the resolution of the promise returned by getPageHtml before continuing to the next instruction. This way, the main function is internally suspended until the asynchronous code completes without blocking the normal execution of the rest of the program. In fact, we will see the string Loading… in the console and, after a moment, the HTML code of the Google landing page.

Isn't this approach much more readable and easy to understand?

Unfortunately, this proposal is not yet final, and even if it will be approved we will need to wait for the next version of the ECMAScript specification to come out and be integrated in Node.js to be able to use this new syntax natively.

So what do we do today? Just wait? No, of course not! We can already leverage async await in our code thanks to transpilers such as Babel.

Installing and running Babel

Babel is a JavaScript compiler (or transpiler) that is able to convert JavaScript code into other JavaScript code using syntax transformers. Syntax transformers allowsthe use of new syntax such as ES2015, ES2016, JSX, and others to produce backward compatible equivalent code that can be executed in modernJavaScript runtimes, such as browsers or Node.js.

You can install Babel in your project using NPM with the following command:

npm install --save-dev babel-cli

We also need to install the extensions to support async await parsing and transformation:

npm install --save-dev babel-plugin-syntax-async-functions babel-plugin-transform-async-to-generator

Now let's assume we want to run our previous example (called index.js).We need to launch the following command:

node_modules/.bin/babel-node --plugins "syntax-async-functions,transform-async-to-generator" index.js

This way, we are transforming the source code in index.js on the fly, applying the transformers to support async await. This new backward compatible code is stored in memory and then executed on the fly on the Node.js runtime.

Babel can also be configured to act as a build processor that stores the generated code into files so that you can easily deploy and run the generated code.

You can read more about how to install and configure Babel on the official website at https://babeljs.io.

Comparison

At this point, we should have a better understanding of the options we have to tame the asynchronous nature of JavaScript. Each one of the solutions presented has its own pros and cons. Let's summarize them in the following table:

Solutions

Pros

Cons

Plain JavaScript

  • Does not require any additional libraries or technology
  • Offers the best performances
  • Provides the best level of compatibility with third-party libraries
  • Allows the creation of ad hoc and more advanced algorithms

Might require extra code and relatively complex algorithms

Async (library)

  • Simplifies the most common control flow patterns
  • Is still a callback-based solution
  • Good performance
  • Introduces an external dependency
  • Might still not be enough for advanced flows
 

Promises

  • Greatly simplify the most common control flow patterns
  • Robust error handling
  • Part of the ES2015 specification
  • Guarantee deferred invocation of onFulfilled and onRejected
  • Require to promisify callback-based APIs
  • Introduce a small performance hit
 

Generators

  • Make non-blocking API look like a blocking one
  • Simplify error handling
  • Part of ES2015 specification
  • Require a complementary control flow library
  • Still require callbacks or promises to implement non-sequential flows
  • Require to thunkify or promisify nongenerator-based APIs
 

Async await

  • Make non-blocking API look like blocking
  • Clean and intuitive syntax
  • Not yet available in JavaScript and Node.js natively
  • Requires Babel or other transpilers and some configuration to be used today
 

It is worth mentioning that we chose to present only the most popular solutions to handle asynchronous control flow, or the ones receiving a lot of momentum, but it's good to know that there are a few more options you might want to look at, for example, Fibers (https://npmjs.org/package/fibers) and Streamline (https://npmjs.org/package/streamline).

Summary

In this article, we analyzed how Babel can be used for performing async await and how to install Babel.

You've been reading an excerpt of:

Node.js Design Patterns - Second Edition

Explore Title
comments powered by Disqus