Asynchronous JavaScript

How to use async/await in JavaScript

Learn how to use async/await in JavaScript, the ES2017 syntax built on promises that makes asynchronous code look synchronous and easier to read and debug.

Introduction

async and await let you write promise-based code that reads top to bottom.

An async function always returns a promise. await pauses that function until a promise settles. The rest of the program keeps running.

See the MDN async function reference for details.

Why were async/await introduced?

Promises beat deep callback nesting. Long .then() chains still get noisy.

async/await maps asynchronous steps onto familiar control flow and normal try/catch.

How it works

const doSomethingAsync = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('I did something'), 3000)
  })
}

Await the promise to read its value:

const doSomething = async () => {
  const result = await doSomethingAsync()
  console.log(result)
}

await only works inside an async function, or at the top level of a module.

A quick example

const doSomethingAsync = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('I did something'), 3000)
  })
}

const doSomething = async () => {
  console.log(await doSomethingAsync())
}

console.log('Before')
doSomething()
console.log('After')

Console output:

Before
After
I did something //after 3s

Promise all the things

Any async function returns a promise, even when you return a plain value:

const aFunction = async () => {
  return 'test'
}

aFunction().then(alert) // This will alert 'test'

That is like:

const aFunction = async () => {
  return Promise.resolve('test')
}

aFunction().then(alert) // This will alert 'test'

Handle errors with try and catch

A rejected awaited promise throws. Catch it with try/catch:

const loadUser = async () => {
  try {
    const response = await fetch('/user.json')

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`)
    }

    return await response.json()
  } catch (error) {
    console.error(error)
  }
}

Uncaught errors reject the promise the async function returns.

The code is much simpler to read

Compare promise chaining for nested fetch steps:

const getFirstUserData = () => {
  return fetch('/users.json') // get users list
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`)
      }

      return response.json()
    })
    .then(users => users[0]) // pick first user
    .then(user => fetch(`/users/${user.name}`)) // get user data
    .then(userResponse => {
      if (!userResponse.ok) {
        throw new Error(`HTTP error: ${userResponse.status}`)
      }

      return userResponse.json()
    })
}

getFirstUserData()

The same flow with async/await:

const getFirstUserData = async () => {
  const response = await fetch('/users.json') // get users list
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`)
  }

  const users = await response.json() // parse JSON
  const user = users[0] // pick first user
  const userResponse = await fetch(`/users/${user.name}`) // get user data
  if (!userResponse.ok) {
    throw new Error(`HTTP error: ${userResponse.status}`)
  }

  const userData = await userResponse.json() // parse JSON
  return userData
}

getFirstUserData()

Multiple async functions in series

Async functions compose cleanly:

const promiseToDoSomething = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('I did something'), 10000)
  })
}

const watchOverSomeoneDoingSomething = async () => {
  const something = await promiseToDoSomething()
  return something + ' and I watched'
}

const watchOverSomeoneWatchingSomeoneDoingSomething = async () => {
  const something = await watchOverSomeoneDoingSomething()
  return something + ' and I watched as well'
}

watchOverSomeoneWatchingSomeoneDoingSomething().then(res => {
  console.log(res)
})

Prints:

I did something and I watched and I watched as well

Run independent work concurrently

Two awaits in a row run one after the other:

const user = await loadUser()
const posts = await loadPosts()

When they do not depend on each other, start both and await Promise.all():

const [user, posts] = await Promise.all([
  loadUser(),
  loadPosts()
])

Easier debugging

Stack traces from async functions are often easier to follow than long promise chains. You are still async: the debugger may pause around each await.

Paste the Before/After example into a page and watch the three log lines arrive in that order.

Lesson completed