HomeGuidesJavaScriptJavaScript Promises & async/await — Complete Guide with Examples
JavaScript

JavaScript Promises and async/await Explained

Asynchronous JavaScript is tested in every senior interview. Here's what you need to know — from callbacks to async/await.

Examifyr·2026·7 min read

Callbacks and callback hell

The original pattern for async code. Deeply nested callbacks become hard to read — this is "callback hell".

// Callback style
fetchUser(userId, (user) => {
    fetchPosts(user.id, (posts) => {
        fetchComments(posts[0].id, (comments) => {
            // deeply nested — hard to read and error-prone
            console.log(comments);
        });
    });
});

Promises

A Promise represents a value that will be available in the future. It can be pending, fulfilled, or rejected.

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        if (success) resolve("data");
        else reject(new Error("failed"));
    }, 1000);
});

promise
    .then(data => console.log(data))    // "data"
    .catch(err => console.error(err))
    .finally(() => console.log("done")); // always runs
Note: .then() returns a new Promise, enabling chaining. .catch() handles any rejection in the chain.

async/await

async/await is syntactic sugar over Promises. An async function always returns a Promise. await pauses execution until the Promise resolves.

async function getUser(id) {
    try {
        const response = await fetch(`/api/users/${id}`);
        if (!response.ok) throw new Error("Not found");
        const user = await response.json();
        return user;
    } catch (err) {
        console.error(err);
        throw err;  // re-throw so callers can handle it
    }
}

// Calling an async function always returns a Promise:
getUser(1).then(user => console.log(user));
Note: await can only be used inside an async function. Using it at the top level requires a module or top-level await (Node.js 14.8+).

Promise.all, Promise.race, Promise.allSettled

These combinators let you handle multiple Promises at once.

const p1 = fetch("/api/users");
const p2 = fetch("/api/posts");
const p3 = fetch("/api/comments");

// Wait for ALL — fails fast if any rejects
const [users, posts, comments] = await Promise.all([p1, p2, p3]);

// Wait for FIRST to settle
const first = await Promise.race([p1, p2, p3]);

// Wait for ALL, get results regardless of success/failure
const results = await Promise.allSettled([p1, p2, p3]);
// [{status: "fulfilled", value: ...}, {status: "rejected", reason: ...}]
Note: Promise.all fails fast — if one Promise rejects, the whole thing rejects immediately. Use Promise.allSettled when you want all results regardless.

Common async pitfalls

These mistakes are tested in interviews.

// PITFALL 1: forgetting await
async function bad() {
    const data = fetch("/api");   // returns Promise, not data!
    console.log(data);            // Promise object, not response
}

// PITFALL 2: sequential when parallel is possible
async function slow() {
    const a = await fetchA();     // waits for A
    const b = await fetchB();     // then waits for B
    // Total time = A + B
}

async function fast() {
    const [a, b] = await Promise.all([fetchA(), fetchB()]);
    // Total time = max(A, B)
}
Note: Unnecessary sequential awaits are a real performance bug. If two async operations don't depend on each other, run them in parallel with Promise.all.

Exam tip

The most common async question: "What's the difference between Promise.all and Promise.allSettled?" — all fails fast on first rejection; allSettled waits for everything and gives you each result.

🎯

Think you're ready? Prove it.

Take the free JavaScript readiness test. Get a score, topic breakdown, and your exact weak areas.

Take the free JavaScript test →

Free · No sign-up · Instant results

← Previous
JavaScript Array Methods — map, filter, reduce & More Explained
Next →
JavaScript ES6+ Features — Destructuring, Spread, Modules & More
← All JavaScript guides