Async/Await: The Complete Guide
Master async/await syntax, error handling patterns, and parallel execution strategies for modern JavaScript applications.
From Callbacks to Async/Await
JavaScript’s approach to asynchronous programming has evolved dramatically. Callbacks led to nested “pyramid of doom” code. Promises flattened the chain. Async/await makes asynchronous code read like synchronous code.
An async function always returns a Promise. The await keyword pauses execution until that Promise settles, then returns the resolved value.
Basic Syntax
async function fetchUser(id) {
const response = await fetch('/api/users/' + id);
const user = await response.json();
return user;
}
// Usage
const user = await fetchUser(42);
console.log(user.name);
Error Handling
Use try/catch blocks to handle rejected promises:
async function fetchUser(id) {
try {
const response = await fetch('/api/users/' + id);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch user:', error.message);
return null;
}
}
Error Handling Patterns
For cleaner code, you can create a utility wrapper that returns a tuple of [error, data]:
async function to(promise) {
try {
const data = await promise;
return [null, data];
} catch (error) {
return [error, null];
}
}
const [error, user] = await to(fetchUser(42));
if (error) handleError(error);
Parallel Execution
Sequential awaits are wasteful when operations are independent. Use Promise.all for parallel execution:
// Sequential (slow - ~2 seconds)
const users = await fetchUsers();
const posts = await fetchPosts();
// Parallel (fast - ~1 second)
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts()
]);
Promise.allSettled
When you need all results regardless of failures, use Promise.allSettled:
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(2),
fetchUser(999) // might fail
]);
results.forEach(result => {
if (result.status === "fulfilled") {
console.log(result.value);
} else {
console.error(result.reason);
}
});
Async Iteration
The for await...of loop handles async iterables elegantly:
async function* generatePages(url) {
let nextUrl = url;
while (nextUrl) {
const response = await fetch(nextUrl);
const data = await response.json();
yield data.items;
nextUrl = data.nextPage;
}
}
for await (const page of generatePages('/api/items')) {
processItems(page);
}
Common Mistakes
Forgetting to await a promise is the most common mistake. The code runs without errors but the value is a Promise object instead of the resolved data. Using a linter rule like no-floating-promises helps catch these.
Summary
Async/await is syntactic sugar over Promises, but it fundamentally improves code readability. Master the patterns here and asynchronous JavaScript becomes manageable.