Asynchronous Adventures in JavaScript: Async/Await

What is Async/Await?

Async/Await also known as Async Functions is a special syntax for controlling asynchronous flow-control in JavaScript. It has been implemented in most Evergreen browsers at the time of writing. it draws inspiration from the C# and F# programming languages. Currently Async/Await has landed in the JavaScript/EcmaScript 2017 version.

Simply defined an async function is a function that returns a Promise. It is also possible to use the await keyword inside of an async function. the Await keyword can be placed in front of an expression which returns a promise, and the value is unwrapped from the Promise in a synchronous looking manner (the Promise still executes asynchronously). Examples often describe things better than words 😁.

// This is a normal function which returns a promise
// which resolves to "MESSAGE" after 2 seconds.
function getMessage() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("MESSAGE"), 2000);
});
}
async function start() {
const message = await getMessage();
return `The message is: ${message}`;
}
start().then(msg => console.log(msg));
// "the message is: MESSAGE"

Why Async/Await?

Async/Await provides a way of writing synchronous looking code that actually executes asynchronously. It also provides a really clean and intuitive way for handling asynchronous errors, because it utilizes try…catch syntax, which is exactly how regular synchronous JavaScript handles errors.

Before we dig into things it should be noted that Async/Await is firmly based on JavaScript Promises, and knowledge of them is necessary to fully understand Async/Await.

SYNTAX

Async Functions

To create an async function simply place the async keyword anywhere before declaring a function, like so:

async function fetchWrapper() {
return fetch('/api/url/');
}

const fetchWrapper = async () => fetch('/api/url/');
const obj = {
async fetchWrapper() {
// ...
}
}

Await Keyword

async function updateBlogPost(postId, modifiedPost) {
const oldPost = await getPost(postId);
const updatedPost = { ...oldPost, ...modifiedPost };
const savedPost = await savePost(updatedPost);
return savedPost;
}

Here the await keyword is used “on” other promise returning functions (which can now be called async functions). In the first line of the function oldPost gets the resolved value of the async function, getPost. On the next line we use the Object spread operator to perform a shallow merge on the oldPost and the modifiedPost. Finally we save the post, and again await the promise that is returned from the asynchronous savePost function.

EXAMPLES / FAQ

“What about handling errors?”

Great question! With async/await we can use the same syntax we use with synchronous code, try...catch. Below if our async call to fetch returns some kind of error, like a 404, the error will be caught by the catch and we can handle the error however we like.

async function tryToFetch() {
try {
const response = await fetch('/api/data', options);
return response.json();
} catch(err) {
console.log(`An error occured: ${err}`);
// Instead of rethrowing the error
// Let's return a regular object with no data
return { data: [] };
}
}
tryToFetch().then(data => console.log(data));
“I’m still not convinced why async/await is better than callbacks/promises.”

Glad you asked! Here’s an example trying to illustrate the differences. Let’s say we want to asynchronously fetchSomeData then sequentially after we have fetched it, we want to asynchronously processSomeData, and if any error occurs, we simply want to return an object.

Callback vs Promise vs Async/Await
“What about Concurrency?”

If we want to do things sequentially it is easy to simply have multiple lines with await statements and pipe the output from one async call into another async call, as is normally done with promises. But in order to understand Concurrency we must use Promise.all. If we have let’s say 3 async actions that we want to happen in parallel (concurrently), we will have to kick off or initiate all of the promises, before await-ing them.

// Not ideal: This will happen sequentially
async function sequential() {
const output1 = await task1();
const output2 = await task2();
const output3 = await task3();
return combineEverything(output1, output2, output3);
}

The reason the above is not ideal, is that we don’t start doing task2 until task1 is done, and we don’t start doing task3 until tasks 1 and 2 are done, but none of the tasks depend on each other. Ideally we want to kick off all 3 of the tasks at the same time. Enter Promise.all.

// Ideal: This will happen concurrently
async function parallel() {
const promises = [
task1(),
task2(),
task3(),
];
const [output1, output2, output3] = await Promise.all(promises);
  return combineEverything(output1, output2, output3);
}

In this example we kick off/initiate all 3 of the asynchronous tasks, and store the promises into an array. Then we await the output of Promise.all() invocation on the array of initiated promises.

Other Gotcha’s

  • It is easy to forget, but anytime you await something, you need to be inside an async function.
  • When you do use await it will only pause the async function that it is contained in. In other words this example will log 'wanna race?' before anything else gets logged.
const timeoutP = async (s) => new Promise((resolve, reject) => {
setTimeout(() => resolve(s*1000), s*1000)
});
[1, 2, 3].forEach(async function(time) {
const ms = await timeoutP(time);
console.log(`This took ${ms} milliseconds`);
});
console.log('wanna race?');

As soon as the first promise is await-ed execution defers back to the main thread and the console.log outside of the forEach is executed.

Browser Support

For a current look at the support check out the can i use browser compatibility table.

Node Support

As of node 7.6.0 and higher Async/Await is supported!

More Posts Like This?

Please join me in this series of Asynchronous Adventures where I will be highlighting how to handle asynchronous code in JavaScript from the basic stuff, to the advanced, fancy strategies.

This series covers in-depth analysis (with examples!) of the following JavaScript patterns: