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)
}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.
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.
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.
Error Handling
We can use try/catch with async/await, just like synchronous code:
This is much cleaner than chaining .catch() on Promises.
Sequential vs Parallel
With async/await, operations are sequential by default:
To run operations in parallel, use Promise.all():
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.
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():
function fetchData(callback) {
setTimeout(() => {
callback("Data received")
}, 1000)
}
fetchData((data) => {
console.log(data)
fetchData((data2) => {
console.log(data2)
fetchData((data3) => {
console.log(data3)
})
})
})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:
asyncfunctions always return Promisesawaitpauses execution until a Promise resolves- Use try/catch for error handling
- Use
Promise.all()for parallel operations - Sequential
awaitstatements create clear, linear flows
Master async/await, and you'll write better asynchronous JavaScript code!