Modern Node.js: async/await syntax


2017-03-01 · 4 min read

async/await syntax greatly simplifies JavaScript code by making it more readable and shorter. It removes the need for many of external flow control libraries to deal with asynchronous code. Regular, synchronous JavaScript constructs such as ifs or fors can be used in most cases, e.g. for based iteration often replaces recursive patterns used with Promise approach. It also unifies the error handling with the vanilla try ... catch construct, a single syntax to handle both synchronous & asynchronous errors. Starting from the version 7.6, Node.js provides native support for async/await.

async function are based on promises, i.e. its return value is always a promise. You can use await only within a function declared as async. Entities being awaited are in fact promises.

async function unexpected() {
  const randomDelay1 = Math.floor(Math.random() * 1000 * 2);
  const randomDelay2 = Math.floor(Math.random() * 1000 * 2);
 
  try {
    const result = await new Promise((resolve, reject) => {
      setTimeout(() => resolve('Hello, World!'), randomDelay1);
      setTimeout(() => reject(new Error('Something went wrong')), randomDelay2);
    });
 
    console.log(result);
  } catch (error) {
    console.log(error.message);
  }
}
 
unexpected();

Using await with any object that has a then() function will also work.

async function awaitable() {
  await { then: resolve => setTimeout(() => resolve(), 1000) };
}
 
awaitable();

A promise either resolves (a slightly fancier way to say it finishes) and returns a value, or it rejects with an error. When using promises directly, the returned value is pushed to .then's callback as the parameter while error are handled by the .catch clause.

// `request` returns a promise
request("http://url.com")
  .then(response => {
    // handle the HTTP response
  })
  .catch(error => {
    // handle error
  })

async/await handles those two cases with synchronous construts i.e. an assignment when a resolved promise returns a value, and try ... catch for exceptions.

try {
  const response = await request("http://url.com");
  // handle the HTTP response
} catch(error) {
  // handle error
}

Chaining .thens is no longer needed. Promises or async functions are supposed to be awaited, otherwise you will get a reference to the promise itself and not its value. The execution will resume once they resolve. Using await on a non-promise entity, simply returns that entity.

Additionally, variables are easier to access as they are available on the same scope and not limited to the scope of a callback from a particular .then that may exist at each step of that promise chain. Asynchronous promise rejections become synchronous errors.

You can only await one thing at a time, which makes the following code to run sequentially

// requests are made one after another
const response1 = await request("http://url-1.com");
const response2 = await request("http://url-2.com");

You could make concurrent requests by only starting those promises and then awaiting returned references, but this approach is not advised as it may be confusing.

// requests are made « at the same time » i.e. concurrently
const request1 = request("http://url-1.com");
const request2 = request("http://url-2.com");
const response1 = await request1;
const response2 = await request2;

You can combine await with promise utilities such as Promise.all or Promise.race which will execute multiple promises concurenly and which provide a better coding pattern for handling concurrency.

Promise.all waits for every promise from an array to resolve and then resolves itself to an array containing the values of resolved promises.

const request = require('request');
 
async function concurrentRequests() {
  const urls = [
    'https://domain1.com',
    'https://domain2.com',
    'https://domain3.com',
  ];
 
  const requests = urls.map(request);
  console.log(await Promise.all(requests));
}
 
concurrentRequests();

Promise.race waits for the first promise from an array to resolve and the resolves itself to a value of that promise. The remaining promises will continue executing as promises are not (yet) cancellable.

async function execute(ms) {
  await new Promise(resolve => setTimeout(() => resolve(), ms));
  console.log('Worker executed for', ms);
}
 
async function main() {
  const workers = [333, 666, 999].map(execute);
  console.log('First who finished', await Promise.race(workers));
}
 
main();

In summary async/await synxtax provides a simple way to write asynchronous code similar to how synchronous code is written. It not only fits well with Node.js asynchronous nature, but it makes writing that asynchonous code much easier.