JavaScript async/await: Modern Async Code
Master async/await syntax. Write cleaner asynchronous code with async functions and await keyword.
JavaScript async/await
The Problem with Promise Chains
Promises work, but they can get messy:
fetch('/api/user')
.then(response => response.json())
.then(user => {
return fetch(`/api/posts/${user.id}`);
})
.then(response => response.json())
.then(posts => {
return fetch(`/api/comments/${posts[0].id}`);
})
.then(response => response.json())
.then(comments => {
console.log(comments);
})
.catch(error => {
console.error(error);
});
Problems:
- Hard to read
- Lots of nesting
- Error handling is at the end
- Feels backwards
The Solution: async/await
async/await makes async code look like regular code:
async function loadData() {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
const commentsResponse = await fetch(`/api/comments/${posts[0].id}`);
const comments = await commentsResponse.json();
console.log(comments);
}
Much easier to read! It reads top to bottom, like regular code.
How async/await Works
Think of await like waiting in line:
┌─────────────────────┐
│ async function │
│ │
│ Step 1: await │ ← Wait here for result
│ fetch('/api/user') │
│ │ │
│ ▼ │
│ Step 2: await │ ← Then continue
│ response.json() │
│ │ │
│ ▼ │
│ Step 3: await │ ← Wait again
│ fetch('/api/posts')│
│ │ │
│ ▼ │
│ Step 4: Use data │ ← Finally use it
└─────────────────────┘
Key point: await pauses the function until the Promise finishes. Then it continues to the next line.
Step-by-Step: Creating an async Function
Step 1: Add async keyword
async function loadData() {
}
Step 2: Use await with Promises
async function loadData() {
const response = await fetch('/api/data');
}
Step 3: Continue with the result
async function loadData() {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
}
What happens:
- Function starts
- Waits for fetch to finish
- Gets the response
- Waits for json() to finish
- Gets the data
- Logs it
Error Handling with try/catch
With async/await, you use try/catch like regular code:
async function loadData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Something went wrong:', error);
return null;
}
}
Flow:
┌─────────────┐
│ try { │
│ await │ ← If this fails...
│ } │
└──────┬──────┘
│
┌──────▼──────┐
│ catch { │ ← ...jump here
│ handle │
│ } │
└─────────────┘
Real Example: Loading User Profile
async function loadUserProfile(userId) {
try {
// Step 1: Get user
const userResponse = await fetch(`/api/users/${userId}`);
if (!userResponse.ok) {
throw new Error('User not found');
}
const user = await userResponse.json();
// Step 2: Get user's posts
const postsResponse = await fetch(`/api/users/${userId}/posts`);
const posts = await postsResponse.json();
// Step 3: Get user's followers
const followersResponse = await fetch(`/api/users/${userId}/followers`);
const followers = await followersResponse.json();
// Step 4: Return everything
return {
user,
posts,
followers
};
} catch (error) {
console.error('Failed to load profile:', error);
throw error;
}
}
// Use it
loadUserProfile(123)
.then(profile => {
console.log('Profile loaded:', profile);
});
Notice: It reads like a story. Step 1, then Step 2, then Step 3. Much clearer than promise chains.
Using async/await with Loops
You can use await in loops:
async function loadMultipleUsers(userIds) {
const users = [];
for (const id of userIds) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
users.push(user);
}
return users;
}
Important: This does them one at a time. If you want them all at once, use Promise.all:
async function loadMultipleUsers(userIds) {
const promises = userIds.map(id =>
fetch(`/api/users/${id}`).then(r => r.json())
);
const users = await Promise.all(promises);
return users;
}
Common Mistakes
Mistake 1: Forgetting await
async function loadData() {
const response = fetch('/api/data'); // Missing await!
const data = response.json(); // This will fail!
}
Mistake 2: Not handling errors
async function loadData() {
const response = await fetch('/api/data'); // What if this fails?
const data = await response.json();
}
Always use try/catch!
Quick Tips
- Use async/await for new code - It's the modern standard
- Always use try/catch - Promises can reject
- await only works in async functions - You can't use it in regular functions
- Use Promise.all for parallel operations - Don't await in a loop if you don't need to
async/await makes async code much easier to read and write. It's what most developers use today. Once you're comfortable with it, you'll rarely go back to promise chains.