Promise Chaining is a technique used to perform a sequence of asynchronous operations one after the other. Each step in the chain starts only when the previous one has successfully finished, passing its result forward.

This is the primary solution to the “Callback Hell” problem, where nested functions make code unreadable and hard to maintain.

1. How the Chain Works

When you return a value from a .then() block, that value is automatically wrapped in a new Promise. This allows you to call another .then() immediately after it.

JavaScript

const getProcess = new Promise((resolve) => {
  resolve(10); // Start with the number 10
});

getProcess
  .then((num) => {
    console.log(num); // 10
    return num * 2;   // Returns 20 to the next .then
  })
  .then((num) => {
    console.log(num); // 20
    return num + 5;   // Returns 25
  })
  .then((finalNum) => {
    console.log(finalNum); // 25
  });

2. Key Rules of Chaining

  • Always return: If you don’t return a value or a promise inside a .then(), the next link in the chain will receive undefined.
  • The Catch-All: A .catch() at the end of the chain will intercept an error from any preceding .then().
  • Transformation: You can use .then() to simply transform data.

JavaScript

Promise.resolve(2)
  .then(val => val * 2) // returns 4
  .then(val => val * 2) // returns 8
  .then(val => console.log(val)); // 8

3. Chaining Asynchronous Tasks

The real power of chaining comes when a .then() returns a new Promise (like a network request). The chain will “pause” and wait for that specific Promise to resolve before moving to the next link.

JavaScript

// Step 1: Get User
fetch('/api/user/1')
  .then(response => response.json()) // Returns a promise with JSON data
  .then(user => {
    console.log("User found:", user.name);
    // Step 2: Use user info to get their posts
    return fetch(`/api/posts/${user.id}`); 
  })
  .then(response => response.json())
  .then(posts => {
    console.log("User posts:", posts);
  })
  .catch(error => {
    console.error("An error occurred somewhere in the chain:", error);
  });

4. Chaining Multiple Asynchronous Calls

The real power comes when you need to fetch data from different sources where each step depends on the previous result.

JavaScript

function getUser(id) {
  return fetch(`https://api.example.com/users/${id}`).then(res => res.json());
}

function getPosts(username) {
  return fetch(`https://api.example.com/posts?user=${username}`).then(res => res.json());
}

// Chaining the operations
getUser(1)
  .then(user => {
    console.log("User found:", user.name);
    return getPosts(user.username); // Returning a new Promise
  })
  .then(posts => {
    console.log("Posts found:", posts.length);
  })
  .catch(error => {
    console.error("An error occurred somewhere in the chain:", error);
  });

5. The Catch-All Error Handling

One of the best features of chaining is that you only need one .catch() at the end. If an error occurs in any of the preceding .then() blocks, the code skips the rest of the chain and jumps straight to the error handler.

Example of Error Bubbling:

JavaScript

doStepOne()
  .then(result => doStepTwo(result))
  .then(result => doStepThree(result))
  .catch(err => {
    // This will catch an error from Step 1, Step 2, OR Step 3
    console.log("Caught:", err);
  });

6. Common Pitfalls to Avoid

A. Forgetting to return

If you don’t return a value or a promise, the next .then() will receive undefined immediately, and the chain will “break” logically (it won’t wait for your async task).

B. Nesting instead of Chaining

A common mistake is nesting promises inside a .then(). If you find yourself doing this, you’ve accidentally recreated callback hell!

JavaScript

// ❌ BAD (Nesting)
fetchUser().then(user => {
  fetchPosts(user).then(posts => {
    // ...
  });
});

// ✅ GOOD (Chaining)
fetchUser()
  .then(user => fetchPosts(user))
  .then(posts => {
    // ...
  });

Categorized in:

Javascript,