Promise Chaining

Med

Promise chaining allows sequential asynchronous operations to be written in a flat, readable structure. Each .then() returns a new Promise, allowing chains of dependent async operations. Returning a value passes it to the next .then(); returning a Promise waits for it to settle. Mastering chaining is key to writing clean async code.

Interactive Visualization

CodeStart
1fetch("/api/user")
2 .then(response => response.json())
3 .then(data => console.log(data));
Promise Pipeline
1
P1 (fetch)
PENDING
Console Output
-
fetch() returns a Promise (P1) - starts pending while network request happens
1 / 5
Key Insight: Each .then() waits for the previous Promise to settle. The chain executes sequentially, step by step.

Key Points

  • .then() always returns a new Promise
  • Return a value to pass it to the next .then()
  • Return a Promise to wait for it before continuing
  • Errors skip to the next .catch()
  • Breaking the chain: forgetting to return
  • Chaining enables sequential async without nesting

Code Examples

Basic Chaining

getUser(1)
  .then(user => {
    console.log('Got user:', user.name);
    return getOrders(user.id); // Return Promise
  })
  .then(orders => {
    console.log('Got orders:', orders.length);
    return orders[0]; // Return value
  })
  .then(firstOrder => {
    console.log('First order:', firstOrder);
  })
  .catch(error => {
    console.error('Any step failed:', error);
  });

// Flat structure, each step depends on previous

Each .then() receives the value returned by the previous .then().

Returning Promises Waits

fetchUser(1)
  .then(user => {
    // Returning a Promise pauses the chain until it settles
    return fetchOrders(user.id); // Waits for orders
  })
  .then(orders => {
    // This runs AFTER fetchOrders completes
    console.log(orders);
    return fetchProducts(orders[0].id);
  })
  .then(products => {
    console.log(products);
  });

// Sequential execution, not parallel

When you return a Promise from .then(), the next .then() waits for it to complete.

The Classic Return Mistake

// ❌ WRONG: Forgetting to return
fetchUser(1)
  .then(user => {
    fetchOrders(user.id); // No return!
  })
  .then(orders => {
    // orders is undefined!
    // This runs immediately, not waiting for fetchOrders
  });

// ✅ CORRECT: Always return
fetchUser(1)
  .then(user => {
    return fetchOrders(user.id); // Return the Promise!
  })
  .then(orders => {
    // orders has the actual data
  });

Forgetting to return breaks the chain. The next .then() receives undefined immediately.

Error Propagation in Chains

step1()
  .then(result1 => {
    console.log('Step 1 done');
    if (result1.invalid) {
      throw new Error('Invalid result');
    }
    return step2(result1);
  })
  .then(result2 => {
    console.log('Step 2 done'); // Skipped if error above
    return step3(result2);
  })
  .then(result3 => {
    console.log('Step 3 done'); // Skipped if error above
  })
  .catch(error => {
    console.error('Failed at some step:', error);
  });

// Errors skip to the next .catch(), skipping intermediate .then()s

When an error occurs, it skips all remaining .then() handlers until a .catch() is found.

Common Mistakes

  • Forgetting to return a value/Promise in .then()
  • Creating nested Promises inside .then() instead of returning
  • Not handling errors that may occur in any step
  • Confusing sequential chains with parallel execution

Interview Tips

  • Always return in .then() to continue the chain
  • Know that returning a Promise waits for it
  • Understand error propagation through chains
  • Be able to refactor nested callbacks to Promise chains