Async and Await

Async and Await are ES7 language features (available from Node 7+) that further simplify syntax of working with Promisified asynchronous functions. Using them we can do couple critical things:

  1. Explicitly declare an asynchronous function with "async" keyword.

  2. Await for other asynchronous functions (the ones declared with async or a one returning promises) in our code. Await halts the execution of our async function, awaiting for the resolution of the promises, but doesn't block the Event Loop, so our code is still as asynchronous as it would be with just using promises.

Please note: you can ONLY use "await" keyword in a function that had been declared async!

Let's see how our example function from the previous chapter will change using async/await:

async function someAsyncFunction(userId) {
  const user = await lookupUser(userId);
  const something = user.something;
  const newUserObj = transform(something, user);
  const transformedUser = await checkCondition(newUserObj);
  if (!transformedUser.passed) {
    throw new Error("Didn't pass the condition!");
  }
  let userFromDB = await saveToDatabase(transformedUser);

  let verify = await verifyDbResult(userFromDB);
  if (!verify) throw new Error ("Database malfunction");

  return userFromDB;
}

// Executing our test function:
async function run() {
  try {
    const result = await someAsyncFunction("7fd12fd1-a977-4ace");
    console.log("SUCCESS: ");
    console.log(result);
  } catch (err) {
    console.error(err.message);
    console.log("Please try again.");
  }
}
run();

Please note that the call to someAsyncFunction had to be moved from main routine into an async function ("run"). Since main routine is not declared as async, we cannot use await there. Also, if we were not checking the result of the call to verifyDbResult, we wouldn't have to call it with an await statement, we could just return the call to it. As Mozilla documentation states:

"There is no need to do an await on the return statement, in an async function, because the return value of an async function is implicitly wrapped in Promise.resolve."

Full source of the example code is at: https://github.com/inadarei/promises123-code/blob/master/ex5.1.js

Chain Parallel with Sequential

Please note that, even though Promises themselves are asynchronous, all await calls, within a single async function, are run in a sequence! In our example code above, the checkCondition will execute only after lookupUser resolves, and saveToDatabase will execute only after checkCondition is resolved, etc. When and if we need to run promises in parallel, within our async function, we need to use a Promise.all explicitly. Let's see how we would implement the "chain parallel with sequential" example we had earlier, using an async function and await calls:

const request = require('request');

function http_get(url) {
  return new Promise((resolve, reject) => {
    request(url, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        resolve(body);
      } else {
        reject(error);
      }
    });
  });
}

// Google Books API Reference: https://developers.google.com/books/docs/v1/using
const base_url = "https://www.googleapis.com/books/v1/volumes?q=";

async function msa_authors() {
  let book_url = `${base_url}isbn:1491956224`; // "Microservice Architecture"
  const book_info = await http_get(book_url);
  const json_response = JSON.parse(book_info);
  const authors = json_response.items[0].volumeInfo.authors;
  let author_promises = [];
  authors.map(author => {
    let author_url = `${base_url}"inauthor:${author}"`;
    author_promises.push(namedRP(author, author_url));
  });

  return Promise.all(author_promises);
}

async function run() {
  try {
    const author_responses = await msa_authors();
    console.log("Final results:");
    console.log(author_responses);
  } catch (err) {
    // http request or parsing or something else failed...
    console.log(err);
  }
}
run();

async function namedRP (name, url) {
  const author_info = await http_get(url);
  json_body = JSON.parse(author_info);
  let response = {};
  response.count = json_body.totalItems;
  response.name = name;
  return response;
};

// Expected output:
//
// Final results:
// [ { name: 'Irakli Nadareishvili', count: 3 },
//   { name: 'Ronnie Mitra', count: 3 },
//   { name: 'Matt McLarty', count: 3 },
//   { name: 'Mike Amundsen', count: 6 } ]

It results in the exact same output as the code without async/await but if you compare this code and the earlier code, you can notice how much cleaner and more readable the latter is.

Side note: we recommend wrapping the code for the promise that goes into the array which Promise.all() will be called onto, in a separate function. In this case namedRP is the async function (returning promises) the calls to which are pushed into the author_promises array. We feel that organizing code this way makes it more modular and leads to much better readability than if we tried to do everything in one place.

Conditions on Synchronous and Asynchronous Calls

When discussing promises, we have shown how to write conditions that have a mix of synchronous and asynchronous code. Following is the same example, but using async/await. As you can see it is completely straightforward and we don't have to use any special tricks for conditions:

const Promise     = require('bluebird');
const rp          = require('request-promise');
const fakepromise = require('fakepromise');

class SomeModel {

  constructor() {
    this.cachedEntity = {};
  }

  async lookupValue() {
    if (!this.isFresh()) {
      console.log("Rereshing entity...")
      let response = await this.refreshEntity();
      response = this.processResponse(response);
      this.cachedEntity = response; // refresh cache
    } else {
      console.log("Entity from cache...")
    }

    // Async functions automatically wrap return values in a promise
    // so we don't have to do it:
    return this.cachedEntity;
  }

  isFresh() {
    if (!this.cachedEntity.lastFresh) {
      return false;
    }

    // or if older than 5 secs - consider not-fresh
    if ((Date.now() - this.cachedEntity.lastFresh) > 5000) {
      return false;
    }

    return true;
  }

  processResponse(response) {
    response.processed = true;
    return response;
  }

  async refreshEntity() {
    const retValue = {};
    retValue.lastFresh = Date.now();
    retValue.value = Math.random() * 700; // 0 - 700
    return fakepromise.promise(200, retValue);
  }
}

async function run() {
  let model = new SomeModel();
  const response1 = await model.lookupValue();
    console.log(response1);
  const response2 = await model.lookupValue();
    console.log(response2)
}
run();

Side note: source files of all examples and instructions for how to execute, are located at: https://github.com/inadarei/promises123-code

Last updated

Was this helpful?