JavaScript10 min read

JavaScript async/await: Modern Async Code

Master async/await syntax. Write cleaner asynchronous code with async functions and await keyword.

Alex Thompson
December 19, 2025
0.0k0

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:

  1. Function starts
  2. Waits for fetch to finish
  3. Gets the response
  4. Waits for json() to finish
  5. Gets the data
  6. 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

  1. Use async/await for new code - It's the modern standard
  2. Always use try/catch - Promises can reject
  3. await only works in async functions - You can't use it in regular functions
  4. 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.

#JavaScript#async/await#Promises#Async#Intermediate