⚡ Async/Await Examples

Master asynchronous JavaScript with practical, real-world examples. Learn how to handle API calls, errors, and parallel execution with clean, readable code.

What is Async/Await?

Async/await is syntactic sugar built on top of Promises, making asynchronous code look and behave more like synchronous code. It was introduced in ES2017 (ES8) and has become the preferred way to handle asynchronous operations in modern JavaScript.

Cleaner, more readable code
Built on Promises
Better error handling with try/catch
Easier to debug than callbacks

Basic Async Function

Beginner

The simplest example of an async function. Any function marked with async automatically returns a Promise.

Use Case: Creating asynchronous functions that return values.
// Basic async function
async function greet() {
  return 'Hello, World!';
}

// Calling async function
greet().then(message => console.log(message));
// Output: Hello, World!

// Using await (must be inside async function)
async function main() {
  const message = await greet();
  console.log(message); // Hello, World!
}

Fetching Data from API

Beginner

The most common use case: fetching data from a REST API using the Fetch API with async/await.

Use Case: Getting user data, posts, products, or any data from an API endpoint.
async function fetchUser(userId) {
  // Fetch data from API
  const response = await fetch(`https://api.example.com/users/${userId}`);
  
  // Parse JSON response
  const user = await response.json();
  
  return user;
}

// Usage
async function displayUser() {
  const user = await fetchUser(1);
  console.log(user.name);
}

Error Handling with Try/Catch

Beginner

Proper error handling is crucial. Use try/catch blocks to gracefully handle errors in async functions.

Use Case: Handling network errors, invalid responses, or any failures in API calls.
async function fetchUserSafely(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    
    // Check if response is OK
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const user = await response.json();
    return user;
    
  } catch (error) {
    console.error('Failed to fetch user:', error.message);
    return null; // Return fallback value
  }
}

// Usage
const user = await fetchUserSafely(1);
if (user) {
  console.log(user.name);
} else {
  console.log('User not found');
}

Pro Tips

  • Always check response.ok before parsing JSON
  • Provide meaningful error messages for debugging
  • Return fallback values when operations fail
  • Log errors for monitoring in production

Sequential vs Parallel Execution

Intermediate

Understanding when to run async operations sequentially (one after another) vs in parallel (simultaneously) can dramatically improve performance.

Use Case: Fetching multiple independent resources efficiently.
// ❌ SLOW: Sequential (one after another)
async function fetchSequential() {
  const user = await fetch('/api/user').then(r => r.json());
  const posts = await fetch('/api/posts').then(r => r.json());
  const comments = await fetch('/api/comments').then(r => r.json());
  
  return { user, posts, comments };
  // Total time: ~3 seconds (1s + 1s + 1s)
}

// ✅ FAST: Parallel (all at once)
async function fetchParallel() {
  const [user, posts, comments] = await Promise.all([
    fetch('/api/user').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/comments').then(r => r.json())
  ]);
  
  return { user, posts, comments };
  // Total time: ~1 second (all requests at once!)
}

When to Use Each

  • Sequential: When one request depends on the result of another
  • Parallel: When requests are independent and can run simultaneously
  • Use Promise.all() for parallel execution - it's much faster!
  • Use Promise.allSettled() if you want all requests to complete even if some fail

POST Request with Data

Intermediate

Sending data to a server using POST requests with async/await.

Use Case: Creating new records, submitting forms, user registration, etc.
async function createUser(userData) {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_TOKEN'
      },
      body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
      throw new Error(`Error: ${response.status}`);
    }
    
    const newUser = await response.json();
    return newUser;
    
  } catch (error) {
    console.error('Failed to create user:', error);
    throw error; // Re-throw for caller to handle
  }
}

// Usage
const newUser = await createUser({
  name: 'John Doe',
  email: 'john@example.com',
  age: 30
});

Retry Logic with Delays

Advanced

Automatically retry failed requests with exponential backoff - a common pattern for handling intermittent failures.

Use Case: Handling unreliable networks, rate-limited APIs, or temporary server issues.
// Helper function to create delay
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i <= maxRetries; i++) {
    try {
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return await response.json();
      
    } catch (error) {
      // If this was the last retry, throw the error
      if (i === maxRetries) {
        throw new Error(`Failed after ${maxRetries} retries: ${error.message}`);
      }
      
      // Calculate delay with exponential backoff
      const delayTime = Math.pow(2, i) * 1000; // 1s, 2s, 4s
      console.log(`Retry ${i + 1}/${maxRetries} after ${delayTime}ms...`);
      
      await delay(delayTime);
    }
  }
}

// Usage
const data = await fetchWithRetry('https://api.example.com/data', 3);

Retry Strategy

  • Exponential Backoff: Delays increase exponentially (1s, 2s, 4s, 8s...)
  • Prevents overwhelming the server with rapid retries
  • Adjust maxRetries based on your use case
  • Consider adding jitter (randomness) to prevent thundering herd

Async Array Operations

Advanced

Processing arrays with async operations efficiently - mapping, filtering, and reducing with async/await.

Use Case: Fetching details for multiple IDs, batch processing, data enrichment.
// Process array items sequentially
async function processSequentially(items) {
  const results = [];
  for (const item of items) {
    const result = await processItem(item);
    results.push(result);
  }
  return results;
}

// Process array items in parallel
async function processInParallel(items) {
  const promises = items.map(item => processItem(item));
  return await Promise.all(promises);
}

// Process with concurrency limit
async function processWithLimit(items, limit = 3) {
  const results = [];
  
  for (let i = 0; i < items.length; i += limit) {
    const batch = items.slice(i, i + limit);
    const batchResults = await Promise.all(
      batch.map(item => processItem(item))
    );
    results.push(...batchResults);
  }
  
  return results;
}

// Example: Fetch user details for multiple IDs
const userIds = [1, 2, 3, 4, 5];
const users = await processInParallel(
  userIds.map(id => fetch(`/api/users/${id}`).then(r => r.json()))
);

Choosing the Right Approach

  • Sequential: When order matters or each depends on the previous
  • Parallel: When items are independent (fastest, but can overwhelm server)
  • Concurrency Limit: Process in batches to balance speed and server load
  • Consider rate limits and server capacity when choosing approach

Timeout Pattern

Advanced

Cancel async operations that take too long using Promise.race() with a timeout.

Use Case: Preventing hung requests, enforcing SLAs, improving UX with loading timeouts.
// Create a timeout promise
function timeout(ms, message = 'Operation timed out') {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error(message)), ms);
  });
}

// Fetch with timeout
async function fetchWithTimeout(url, timeoutMs = 5000) {
  try {
    const response = await Promise.race([
      fetch(url),
      timeout(timeoutMs, `Request timed out after ${timeoutMs}ms`)
    ]);
    
    return await response.json();
    
  } catch (error) {
    if (error.message.includes('timed out')) {
      console.error('Request was too slow');
    }
    throw error;
  }
}

// Usage with 3 second timeout
const data = await fetchWithTimeout('https://api.example.com/slow-endpoint', 3000);