An Introduction to Async / Await

An Introduction to Async / Await

Written by Paul McBride and edited by Michael Okoh

Writing asynchronous code in JavaScript can be challenging. Until recently, we relied on callback functions when writing asynchronous code. Callbacks work well, but they often result in deeply nested code and code that resembles the infamous pyramid of doom.

In modern JavaScript, we were rescued from callback hell by Promises. Promises are an elegant way to handle asynchronous code. A Promise represents the eventual result of an asynchronous operation. It will either resolve the expected value or a reason for failure.

In this article, we're going to look at the Fetch API. It is a promise based API for making asynchronous network requests. First, we'll use Fetch as a standard promise chain, then we'll look at how to refactor it using Async/Await.

Check out this article if you would like to see how the Fetch API compares to the XMLHttpRequest API

Using Promises

Let's start by using fetch to pull some data from the GitHub API.

function fetchGithubData() {
  return fetch('https://api.github.com/users/thePaulMcBride')
    .then(response => response.json())
    .then(body => console.log(body))
}

This is a fairly simple example. It fetches my github profile, parses the JSON response, then logs the value to the console. Currently there is no error handling, but we'll cover that later.

Using Async/Await

Let's take a look at how we could refactor this to use async/await. Before we dive into an example, we'll talk a little about how to use async/await. Firstly, when defining a function, the keyword async should be placed before the function keyword.

    async function example() {
     // async code here
    }

When writting arrow functions, the async keyword is placed before the arguement list.

    const example = async () => {
     // async code here
    }

Next, let's look at the await keyword. When placed in front of a promise call, it pauses the execution of an async function and waits for the promise to resolve. It only works when placed in front of a promise and only when used in an async function.

Now that we know how to use async/await, we can use it to refactor our Github function from earlier.

    async function asyncFetchGithubData() {
      const response = await fetch('https://api.github.com/users/thePaulMcBride')
    
      const body = await response.json()
    
      console.log(body)
    }

This function is used in exactly the same way as the previous function. However, because we are using async/await it reads like synchronous code and is a little easier to read at a glance.

Error handling

Error handling with promises is trivial. You simply call catch on the end of your promise chain. Let's do that with our first example.

    function fetchGithubData() {
      return fetch('https://api.github.com/users/thePaulMcBride')
        .then(response => response.json())
        .then(body => console.log(body))
        .catch(e => console.log(e.message))
    }

This function will not handle any errors that may occur when it executes. The way we handle errors using async/await is just as simple. In fact, we have two options. First, let's look at using a try-catch block.

    async function asyncFetchGithubData() {
      try {
        const response = await fetch('https://api.github.com/users/thePaulMcBride')
    
        const body = await response.json()
    
        console.log(body)
      } catch(e) {
        console.log(e.message)
      }
    }

Now, if an error occurs in the try block, it will stop executing and go to the catch block. The other way to handle errors with async functions is by chaining a catch on to the function call.

    async function asyncFetchGithubData() {
      const response = await fetch('https://api.github.com/users/thePaulMcBride')
    
      const body = await response.json()
    
      console.log(body)
    }
    
    asyncFetchGithubData().catch(e => console.log(e.message))

Multiple functions

Now that we know how to write async functions, let's look at a more complicated example. We will write an async function to fetch two different GitHub users. Initally, we'll do this in a quick and easy way, then look at improving it.

In this example, we will be using a helper library called Axios. It does a lot of the same things as fetch, except, it parses the body of the response for us.

    async function asyncFetchGithubData() {
      const paul = await axios('https://api.github.com/users/thePaulMcBride')
      const wes = await axios('https://api.github.com/users/wesbos')
    
    
      console.log(paul.name, wes.name)
    }

Pretty simple code, right? The problem is that the two separate API calls happen one after the other. It doesn't make the second API call until the first one is complete. Let's look at how we can improve this.

    async function asyncFetchGithubData() {
      // Fire both API requests at the same time
      const paulPromise = axios('https://api.github.com/users/thePaulMcBride')
      const wesPromise = axios('https://api.github.com/users/wesbos')
    
      // Wait for both requests to resolve
      const [paul, wes] = await Promise.all([paulPromise, wesPromise])
    
      console.log(paul.name, wes.name)
    }

Notice how we no longer await the API calls individually? Instead, we use the Promise.all helper to wait for both of them to resolve. Of course, everything we've just written is possible without async/await, but by using it, our code is easier to read and reason about.

Support

As we now know, async/await is incredibly useful. However, if it isn't widely supported, then what is the point in using it?

Well, as it turns out, support is pretty good. At the time of writing, async functions are supported by all modern browsers except IE 11. If you have to support IE11 or older and you're using babel, it's simple to add support. [This article explains how] (https://medium.com/@zwacky/add-es7-async-await-support-into-your-non-bleeding-edge-build-process-ad0dded0d002).

If you are using Node.js, support was enabled by default in version 7.6, so as long as you are using at least that version, you'll have no problems.

Conclusion

Async functions mean we can now write code which looks synchronous. This means our code can be easier to read and reason about. Node and browser support is good and there are tools available to help support older browsers.

Try out async/await and you'll be writing clean, succinct code in no time.

Basic Routing, HTTP Requests, and CRUD Operations with Express and MongoDB

Basic Routing, HTTP Requests, and CRUD Operations with Express and MongoDB

Building Your First Node App Using Docker

Building Your First Node App Using Docker