← Back
2/15/2024·coding

Async/await is a modern way to handle asynchronous operations in JavaScript. It makes asynchronous code look and behave more like synchronous code, making it easier to read and understand.

In this scrollycoding tutorial, we'll break down async/await patterns step by step, starting with promises and moving to more complex scenarios.

The Problem with Callbacks

Before async/await, we used callbacks, which led to "callback hell":

This nested structure is hard to read and maintain. Promises helped, but async/await makes it even better.

function fetchData(callback) {
  setTimeout(() => {
    callback("Data received")
  }, 1000)
}

fetchData((data) => {
  console.log(data)
  fetchData((data2) => {
    console.log(data2)
    fetchData((data3) => {
      console.log(data3)
    })
  })
})

Basic Async Function

An async function always returns a Promise. Here's the simplest example:

Even though we're returning a string, the function automatically wraps it in a Promise.

async function fetchData() {
  return "Data received"
}

fetchData().then(data => {
  console.log(data) // "Data received"
})

Using Await

The await keyword pauses the execution of an async function until a Promise resolves:

The code looks synchronous, but it's actually asynchronous. The function pauses at await and resumes when the Promise resolves.

async function fetchData() {
  const data = await new Promise((resolve) => {
    setTimeout(() => resolve("Data received"), 1000)
  })
  console.log(data) // "Data received" (after 1 second)
  return data
}

fetchData()

Error Handling

We can use try/catch with async/await, just like synchronous code:

This is much cleaner than chaining .catch() on Promises.

async function fetchData() {
  try {
    const data = await fetch("https://api.example.com/data")
    const json = await data.json()
    return json
  } catch (error) {
    console.error("Error:", error)
    throw error
  }
}

fetchData().catch(error => {
  console.log("Caught:", error)
})

Sequential vs Parallel

With async/await, operations are sequential by default:

To run operations in parallel, use Promise.all():

async function fetchParallel() {
  const [data1, data2] = await Promise.all([
    fetch("https://api.example.com/data1"),
    fetch("https://api.example.com/data2")
  ])
  // Both fetch simultaneously
  return [data1, data2]
}

Multiple Awaits

You can use multiple await statements in sequence:

Each await waits for the previous one to complete. This creates a clear, linear flow.

async function processData() {
  const user = await fetchUser(1)
  const posts = await fetchPosts(user.id)
  const comments = await fetchComments(posts[0].id)
  
  return {
    user,
    posts,
    comments
  }
}

Await in Loops

When using await in loops, each iteration waits for the previous one:

If you want to process items in parallel, use Promise.all():

async function processItemsParallel(items) {
  const promises = items.map(item => processItem(item))
  return await Promise.all(promises)
}

Async/await makes asynchronous JavaScript code much more readable and maintainable. It combines the power of Promises with the readability of synchronous code.

Key takeaways:

  • async functions always return Promises
  • await pauses execution until a Promise resolves
  • Use try/catch for error handling
  • Use Promise.all() for parallel operations
  • Sequential await statements create clear, linear flows

Master async/await, and you'll write better asynchronous JavaScript code!