Intermediate JavaScript / Asynchronous Programming

Async/Await: The Complete Guide

Master async/await syntax, error handling patterns, and parallel execution strategies for modern JavaScript applications.

2 min read Updated Feb 12, 2026 Reviewed Jan 28, 2026

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.