DISCOVERY

November 26th, 2017

JavaScript Async Functions: Combining Promises and Generators

JavaScript

ECMAScript 6

ECMAScript 2017

Asynchronous Programming

Promises

Generators

Async Function

Node.js

In previous posts I looked at Promises and Generators in JavaScript. I mentioned these two new ES6 features are especially powerful when combined. I do think that Promises on their own are very useful, however I am not yet sold on Generators.

By combining Promises and Generators, we can create a function that handles asynchronous tasks (let's call it async). This function takes one parameter - a generator function. Each yield statement in the generator returns a promise, which calls the generators iterator once resolved. Therefore each asynchronous task in the async function moves on to the next task in sequential order. Let's look at some pseudocode:

function async(generator) { var iterator = generator(); await(iterator.next()) function await(promise) { promise.then(function() { await(iterator.next()); }) } }

This code lacks error handling and other technicalities present in production code, but displays the basic idea of the async function. The following pseudocode uses async() to execute HTTP GET requests.

async(function *tasks() { yield httpGet("www.example.com/search/1"); yield httpGet("www.example.com/search/2"); yield httpGet("www.example.com/search/3"); })

With the reusable async function, asynchronous code is very simple! Even better, its written in a synchronous manner with no promises or dreaded callbacks to deal with! All of the Promise code is abstracted away. So why am I only showing pseudocode and not the real thing? The reason is because ECMAScript 2017 (or following the old naming convention ES8) implemented this pattern using the new async and await keywords1.

In my discovery on promises I created a google search API for cat posts. Let's refactor this example using an ES2017 async function. I used Node 9 for the refactor to avoid transpiling my code with Babel. One thing that Node 9 (the newest version of Node.js as of November 2017) does not support by default is ES6 modules. As a workaround, the --experimental-modules flag can be used to enable ES6 modules2. To notify Node.js that files are modules, the .mjs file extension is used. These files are hilariously referred to as Micheal Jackson Scripts. Hopefully the .mjs files are just for the experimental version and not here to stay - I would miss those .js files (and it doesn't seem like an elegant solution)!

Now let's get to the code. The first module creates a promise for a request to the google search API3. The second module imports the first module and uses the ES2017 async function:

import {default as https} from 'https'; export function search(query = 'cat') { const url = 'https://www.googleapis.com/customsearch/v1?key=AIzaSyA2QIPJoGYMx_DuQH6wDqNG3AHXG7bcb94' + '&cx=005720647135116730303:chkg-pnyocg&q=' + query + '&prettyPrint=false'; return new Promise(function (resolve, reject) { https.get(url, res => { res.setEncoding('utf-8'); let response = ''; res.on('data', data => { response += data; }); res.on('end', () => { resolve(JSON.parse(response)); }); }); }); }
import * as http from './meowHttp'; (async function search() { let catResult = await http.search(); print(catResult); let meowResult = await http.search('meow'); print(meowResult); })();

You may have noticed that the async and await keywords are simply syntactic sugar for our async() and await() functions. This is a truly elegant solution that allows asynchronous code to be written in an easily understandable synchronous manner. Executing this code results in the following:

The ES2017 async function is very powerful! Although you never need to fully understand the details of combined Promises and Generators when using async functions, it is still good to know what is going on behind the scenes! All the code from this discovery is on GitHub.

[1] "async function", https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

[2] "Node.js v9.2.0 Documentation: ECMAScript Modules", https://nodejs.org/api/esm.html#esm_enabling

[3] "Node.js v9.2.0 Documentation: HTTPS", https://nodejs.org/api/https.html