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.
Once the Web App is ready, we can click on "Deployment Options" and follow the instructions to select our GitHub repo to deploy from:
Now we should see the deployment starting:
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:
This is because App Service doesn't default to the newest version of Node. But that's easily fixed by changing the application settings:
We can even confirm the node version in the App Service console by typing node -v
:
Now the app should work!
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.
Once the Web App is ready, we can click on "Deployment Options" and follow the instructions to select our GitHub repo to deploy from:
Now we should see the deployment starting:
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:
This is because App Service doesn't default to the newest version of Node. But that's easily fixed by changing the application settings:
We can even confirm the node version in the App Service console by typing node -v
:
Now the app should work!