Async/await is a modern way of writing asynchronous functions in JavaScript. They are built on top of promises and allow us to write asynchronous code synchronously.

Why Async/await?

Promises are great for writing asynchronous code and have solved the famous callback hell problem, but they also introduced their own complexities. The code quickly becomes complicated if we chain multiple promises to perform a complex task.

Async/await was introduced in ES2017 (ES8) to reduce the extra boilerplate code around promises. It drastically improves the code readability and makes it appears synchronous while working asynchronously and non-blocking behind the scene.

Async Function

To create an async function, all you need to do is add the async keyword before the function definition:

const sayHi = async () => {
  return 'Hey dev 👋'
}

The async keyword always means one (and just one) thing before any function: it returns a promise. Even if it explicitly returns a simple value, like the above, the returned value is automatically enclosed inside a resolved promise. Since the value returned is a promise, we can do something like the below to get the value:

sayHi().then(greeting => console.log(greeting)) // Hey dev 👋

Await

The await keyword can only be used within an async function. Otherwise, it will throw a syntax error. It waits for a promise to resolve or gets rejected.

Here is an example:

const greet = async () => {
  let message = await sayHi()
  console.log(message)
}

greet() // Hey dev 👋

Essentially, the await keyword makes JavaScript pauses the function execution until the promise is settled and resumes when the result is available. It is better than promise.then() and makes the code easier to understand and write.

Examples

Here is a quick example of async/await for writing an asynchronous function:

const cage = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('🐦')
    }, 1000)
  })
}

const planet = async () => {
  const bird = await cage()
  console.log('Bird:', bird)
}

planet() // Bird: 🐦 (after 0.5s)

In the example above, we first declare a function that returns a new promise that resolves to a value after one second. We then create an async function and wait for the promise to resolve before logging the returned value to the console.

The power of async/await functions becomes more apparent when multiple asynchronous functions are involved:

const eagle = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('🦅')
    }, 200)
  })
}

const duck = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('🦆')
    }, 400)
  })
}

const owl = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('🦉')
    }, 600)
  })
}

const planet = async () => {
  const e = await eagle()
  const d = await duck()
  const o = await owl()

  console.log(`Birds: ${e} ${d} ${o}`)
}

planet() // Birds: 🦅 🦆 🦉

In the above example, each async function is called sequentially. Every promise is waiting for the promise before (if available) to either resolve or reject before continuing. If you want to execute these promises in parallel, you can simply use Promise.all() to wait until all of them are ready:

// ...
const planet = async () => {
  const [e, d, o] = await Promise.all([eagle(), duck(), owl()])

  console.log(`Birds: ${e} ${d} ${o}`)
}

planet() // Birds: 🦅 🦆 🦉

Error Handling

Another benefit of using async/await functions is that both asynchronous and synchronous errors are handled in the same construct, using good old try...catch statements. All you need to do is, put your code in a try block, and the catch block will automatically capture all errors:

const isEven = num => {
  return new Promise((resolve, reject) => {
    if (num % 2 == 0) {
      resolve('👌')
    } else {
      reject('💀')
    }
  })
}

const verify = async num => {
  try {
    const sign = await isEven(num)
    console.log(sign)
  } catch (err) {
    console.log(err)
  }
}

verify(8) // 👌
verify(15) // 💀
verify('abc') // 💀

This synchronous error handling doesn't just work for promises, but it also works when there is an actual syntax or runtime error, as you can see above for verify('abc').

Don't want to use try...catch? Since the async function returns a promise, you can also use the catch() method to deal with unhandled errors:

// ...
const verify = async num => {
  const sign = await isEven(num)
  console.log(sign)
}

verify('hey').catch(err => console.log(err)) // 💀

Summary

Async/await is a new way of writing asynchronous code in JavaScript. Before this, we used callbacks and promises for asynchronous code. It allows us to write a synchronous-looking code that is easier to maintain and understand.

  • Async/await is non-blocking, built on top of promises, and can't be used in plain callbacks.
  • The async function always returns a promise.
  • The await keyword is used to wait for the promise to settle. It can only be used inside an async function.
  • A try...catch block can be used to capture both asynchronous and synchronous (syntax, runtime) errors inside an async function.

✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.