Tutorial

Exploring Async/Await Functions in JavaScript

Published on September 4, 2020
author

Alligator.io

Exploring Async/Await Functions in JavaScript

Introduction

Promises give us an easier way to deal with asynchrony in our code in a sequential manner. Considering that our brains are not designed to deal with asynchronicity efficiently, this is a much welcome addition. Async/await functions, a new addition with ES2017 (ES8), help us even more in allowing us to write completely synchronous-looking code while performing asynchronous tasks behind the scenes.

The functionality achieved using async functions can be recreated by combining promises with generators, but async functions give us what we need without any extra boilerplate code.

Simple Example

In the following example, we first declare a function that returns a promise that resolves to a value of 🤡 after 2 seconds. We then declare an async function and await for the promise to resolve before logging the message to the console:

function scaryClown() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('🤡');
    }, 2000);
  });
}

async function msg() {
  const msg = await scaryClown();
  console.log('Message:', msg);
}

msg(); // Message: 🤡 <-- after 2 seconds

await is a new operator used to wait for a promise to resolve or reject. It can only be used inside an async function.

The power of async functions becomes more evident when there are multiple steps involved:

function who() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('🤡');
    }, 200);
  });
}

function what() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('lurks');
    }, 300);
  });
}

function where() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('in the shadows');
    }, 500);
  });
}

async function msg() {
  const a = await who();
  const b = await what();
  const c = await where();

  console.log(`${ a } ${ b } ${ c }`);
}

msg(); // 🤡 lurks in the shadows <-- after 1 second

A word of caution however, in the above example each step is done sequentially, with each additional step waiting for the step before to resolve or reject before continuing. If you instead want the steps to happen in parallel, you can simply use Promise.all to wait for all the promises to have fulfilled:

// ...

async function msg() {
  const [a, b, c] = await Promise.all([who(), what(), where()]);

  console.log(`${ a } ${ b } ${ c }`);
}

msg(); // 🤡 lurks in the shadows <-- after 500ms

Promise.all returns an array with the resolved values once all the passed-in promises have resolved.

In the above we also make use of some nice array destructuring to make our code succinct.

Promise-Returning

Async functions always return a promise, so the following may not produce the result you’re after:

async function hello() {
  return 'Hello Alligator!';
}

const b = hello();

console.log(b); // [object Promise] { ... }

Since what’s returned is a promise, you could do something like this instead:

async function hello() {
  return 'Hello Alligator!';
}

const b = hello();

b.then(x => console.log(x)); // Hello Alligator!

…or just this:

async function hello() {
  return 'Hello Alligator!';
}

hello().then(x => console.log(x)); // Hello Alligator!

Different Forms

So far with our examples we saw the async function as a function declaration, but we can also define async function expressions and async arrow functions:

Async Function Expression

Here’s the async function from our first example, but defined as a function expression:

const msg = async function() {
  const msg = await scaryClown();
  console.log('Message:', msg);
}

Async Arrow Function

Here’s that same example once again, but this time defined as an arrow function:

const msg = async () => {
  const msg = await scaryClown();
  console.log('Message:', msg);
}

Error Handling

Something else that’s very nice about async functions is that error handling is also done completely synchronously, using good old try…catch statements. Let’s demonstrate by using a promise that will reject half the time:

function yayOrNay() {
  return new Promise((resolve, reject) => {
    const val = Math.round(Math.random() * 1); // 0 or 1, at random

    val ? resolve('Lucky!!') : reject('Nope 😠');
  });
}

async function msg() {
  try {
    const msg = await yayOrNay();
    console.log(msg);
  } catch(err) {
    console.log(err);
  }
}

msg(); // Lucky!!
msg(); // Lucky!!
msg(); // Lucky!!
msg(); // Nope 😠
msg(); // Lucky!!
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Lucky!!

Given that async functions always return a promise, you can also deal with unhandled errors as you would normally using a catch statement:

async function msg() {
  const msg = await yayOrNay();
  console.log(msg);
}

msg().catch(x => console.log(x));

This synchronous error handling doesn’t just work when a promise is rejected, but also when there’s an actual runtime or syntax error happening. In the following example, the second time with call our msg function we pass in a number value that doesn’t have a toUpperCase method in its prototype chain. Our try…catch block catches that error just as well:

function caserUpper(val) {
  return new Promise((resolve, reject) => {
    resolve(val.toUpperCase());
  });
}

async function msg(x) {
  try {
    const msg = await caserUpper(x);
    console.log(msg);
  } catch(err) {
    console.log('Ohh no:', err.message);
  }
}

msg('Hello'); // HELLO
msg(34); // Ohh no: val.toUpperCase is not a function

Async Functions With Promise-Based APIS

As we showed in our primer to the Fetch API, web APIs that are promise-based are a perfect candidate for async functions:

async function fetchUsers(endpoint) {
  const res = await fetch(endpoint);
  let data = await res.json();

  data = data.map(user => user.username);

  console.log(data);
}

fetchUsers('https://jsonplaceholder.typicode.com/users');
// ["Bret", "Antonette", "Samantha", "Karianne", "Kamren", "Leopoldo_Corkery", "Elwyn.Skiles", "Maxime_Nienow", "Delphine", "Moriah.Stanton"]

Browser Support: As of 2020, 94% of browsers worldwide can handle async/await in javascript Notable exceptions are IE11 and Opera Mini.

Conclusion

Before Async/await functions, JavaScript code that relied on lots of asynchronous events (for example: code that made lots of calls to APIs) would end up in what some called “callback hell” - A chain of functions and callbacks that was very difficult to read and understand.

Async and await allow us to write asynchronous JavaScript code that reads much more clearly.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Alligator.io

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Please fix the typo: “but you we can also”

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.