Skip to content

Async Await

In addition to Promises, there is a new syntax you might encounter to handle asynchronous code named async / await.

The purpose of async/await functions is to simplify the behavior of using promises synchronously and to perform some behavior on a group of Promises. Just as Promises are similar to structured callbacks, async/await is similar to combining generators and promises. Async functions always return a Promise. (Ref: MDN)

Note : You must understand what promises are and how they work before trying to understand async / await since they rely on it.

Note 2: await must be used in an async function, which means that you can't use await in the top level of our code since that is not inside an async function.

Sample code

1
2
3
4
5
6
7
8
async function getGithubUser(username) { // async keyword allows usage of await in the function and means function returns a promise
  const response = await fetch(`https://api.github.com/users/${username}`); // Execution is paused here until the Promise returned by fetch is resolved
  return response.json();
}

getGithubUser('mbeaudru')
  .then(user => console.log(user)) // logging user response - cannot use await syntax since this code isn't in async function
  .catch(err => console.log(err)); // if an error is thrown in our async function, we will catch it here

Explanation with sample code

Async / Await is built on promises but they allow a more imperative style of code.

The async operator marks a function as asynchronous and will always return a Promise. You can use the await operator in an async function to pause execution on that line until the returned Promise from the expression either resolves or rejects.

1
2
3
4
5
6
async function myFunc() {
  // we can use await operator because this function is async
  return "hello world";
}

myFunc().then(msg => console.log(msg)) // "hello world" -- myFunc's return value is turned into a promise because of async operator

When the return statement of an async function is reached, the Promise is fulfilled with the value returned. If an error is thrown inside an async function, the Promise state will turn to rejected. If no value is returned from an async function, a Promise is still returned and resolves with no value when execution of the async function is complete.

await operator is used to wait for a Promise to be fulfilled and can only be used inside an async function body. When encountered, the code execution is paused until the promise is fulfilled.

Note : fetch is a function that returns a Promise that allows to do an AJAX request

Let's see how we could fetch a github user with promises first:

1
2
3
4
5
6
7
function getGithubUser(username) {
  return fetch(`https://api.github.com/users/${username}`).then(response => response.json());
}

getGithubUser('mbeaudru')
  .then(user => console.log(user))
  .catch(err => console.log(err));

Here's the async / await equivalent:

1
2
3
4
5
6
7
8
async function getGithubUser(username) { // promise + await keyword usage allowed
  const response = await fetch(`https://api.github.com/users/${username}`); // Execution stops here until fetch promise is fulfilled
  return response.json();
}

getGithubUser('mbeaudru')
  .then(user => console.log(user))
  .catch(err => console.log(err));

async / await syntax is particularly convenient when you need to chain promises that are interdependent.

For instance, if you need to get a token in order to be able to fetch a blog post on a database and then the author informations:

Note : await expressions needs to be wrapped in parentheses to call its resolved value's methods and properties on the same line.

async function fetchPostById(postId) {
  const token = (await fetch('token_url')).json().token;
  const post = (await fetch(`/posts/${postId}?token=${token}`)).json();
  const author = (await fetch(`/users/${post.authorId}`)).json();

  post.author = author;
  return post;
}

fetchPostById('gzIrzeo64')
  .then(post => console.log(post))
  .catch(err => console.log(err));

Error handling

Unless we add try / catch blocks around await expressions, uncaught exceptions – regardless of whether they were thrown in the body of your async function or while it’s suspended during await – will reject the promise returned by the async function. Using the throw statement in an async function is the same as returning a Promise that rejects. (Ref: PonyFoo).

Note : Promises behave the same!

With promises, here is how you would handle the error chain:

function getUser() { // This promise will be rejected!
  return new Promise((res, rej) => rej("User not found !"));
}

function getAvatarByUsername(userId) {
  return getUser(userId).then(user => user.avatar);
}

function getUserAvatar(username) {
  return getAvatarByUsername(username).then(avatar => ({ username, avatar }));
}

getUserAvatar('mbeaudru')
  .then(res => console.log(res))
  .catch(err => console.log(err)); // "User not found !"

The equivalent with async / await:

async function getUser() { // The returned promise will be rejected!
  throw "User not found !";
}

async function getAvatarByUsername(userId) => {
  const user = await getUser(userId);
  return user.avatar;
}

async function getUserAvatar(username) {
  var avatar = await getAvatarByUsername(username);
  return { username, avatar };
}

getUserAvatar('mbeaudru')
  .then(res => console.log(res))
  .catch(err => console.log(err)); // "User not found !"

External resources