Anthony Chu Contact Me

Using ES2017 async/await in Node

Thursday, February 23, 2017

A couple of days ago, Node 7.6.0 was released. One of the changes was an update to version 5.5 of the V8 JavaScript engine. This meant that async/await support is now enabled by default! Finally, we can use the async and await keywords in Node without the need for a transpiler or turning on command line flags.

And what's better is that Azure App Service has already added support for Node 7.6.0.

Today we'll create a simple application to explore async/await in Node. Then we'll deploy it to Azure App Service.

Promises

Promises came to JavaScript in ES6. Compared to traditional callbacks, they allow for cleaner code (less callback hell) and simpler error handling.

Here's a simple app using promises that fetches a user's profile from the GitHub API, and then fetches their repos and follower lists.

const express = require('express');
const fetch = require('node-fetch');

const app = express();

// promises version

function getUser(username) {
  return fetch('https://api.github.com/users/' + username)
    .then(res => {
      return res.json()
        .then(user => {
          return { user, found: res.status === 200 }
        });
    });
}

function getObject(url) {
  return fetch(url).then(res => res.json());
}

app.get('/api/promises/users/:username', (req, res) => {
  const {username} = req.params;
  getUser(username)
    .then(userResult => {
      if (!userResult.found) {
        res.status(404).end();
        return;
      }

      const {user} = userResult;
      const {repos_url, followers_url} = user;
      return Promise.all([getObject(repos_url), getObject(followers_url)])
        .then(results => {
          const [repos, followers] = results;
          user.repos = repos;
          user.followers = followers;
          res.send(user);
        })
    })
    .catch(e => res.status(500).end());
});

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`Example app listening on port ${port}!`));

There are a couple of places where promises are nested. It doesn't really make that code too hard to read, but it does make it easy to make mistakes. For instance, we have to remember to return the inner promises, for example; it can lead to hidden bugs if we miss one.

async/await

Now let's convert the app to use async/await. Don't forget, we'll need Node 7.6.0 or higher to run this:

const express = require('express');
const fetch = require('node-fetch');

const app = express();

// async/await version

async function getUserAsync(username) {
  const res = await fetch('https://api.github.com/users/' + username);
  return { user: await res.json(), found: res.status === 200 };
}

async function getObjectAsync(url) {
  return (await fetch(url)).json();
}

app.get('/api/async-await/users/:username', async (req, res) => {
  try {
    const {username} = req.params;
    const userResult = await getUserAsync(username);

    if (!userResult.found) {
      res.status(404).end();
      return;
    }

    const {user} = userResult;
    const {repos_url, followers_url} = user;
    const [repos, followers] = 
      await Promise.all([getObjectAsync(repos_url), getObjectAsync(followers_url)]);

    user.repos = repos;
    user.followers = followers;

    res.send(user);
  } catch (e) {
    res.status(500).end();
  }
});

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`Example app listening on port ${port}!`));

This code is a lot cleaner, in my opinion. The getUserAsync() function uses await to "unwrap" the promise from res.json(). The original code had to use a nested promise. Same thing with the made body of the application, it now looks like synchronous code and that makes it really easy to reason about.

For exception handling, we can use the more traditional try/catch block.

Node 7.6.0 in Azure App Service

It's been less than two days since Node 7.6.0 came out and Azure App Service already supports it.

The code for this application is located at https://github.com/anthonychu/node-async-await. We'll create a Web App in Azure App Service and deploy it from this repo.

The first step we need to do is create the Web App. This will work in all App Service plans, including the free one.

New Web App

Once the Web App is ready, we can click on "Deployment Options" and follow the instructions to select our GitHub repo to deploy from:

Set up continuous deployment

Now we should see the deployment starting:

Deploying...

It should only take a minute or so. App Service automatically does an npm install and detects our application's main startup script. It will also deploy the application every time we push a change to GitHub.

Now if we go to the URL of the app, we get a 500 error:

500 error

This is because App Service doesn't default to the newest version of Node. But that's easily fixed by changing the application settings:

Select node version

We can even confirm the node version in the App Service console by typing node -v:

Node version

Now the app should work!

Working