Defeating Deep Nesting

The superpower of Promises is that .then() always returns a new Promise. This lets you chain operations sequentially in a flat, readable structure — no nesting required.

Flat and Readable

fetch('/user.json')
  .then(response => response.json())   // Step 1: Parse the JSON body
  .then(user => fetch('/avatar/' + user.id)) // Step 2: Fetch the avatar
  .then(response => response.blob())   // Step 3: Get the image data
  .then(blob => {
    let img = document.createElement('img');
    img.src = URL.createObjectURL(blob);
    document.body.append(img);         // Step 4: Show the image
  })
  .catch(err => console.error('Something went wrong:', err));

How Returning Works

  • If you return a value from .then(), the next .then() receives it immediately.
  • If you return a new Promise, the next .then() waits for it to settle.
new Promise(resolve => resolve(1))
  .then(result => result * 2)  // returns 2
  .then(result => result * 2)  // returns 4
  .then(result => console.log(result)); // logs: 4
Tip

Each step in the chain receives the return value of the previous step. This is what makes chaining powerful for sequential async operations.

Caution

Don't confuse chaining with attaching multiple .then() calls to the same promise object. In a chain, each .then() receives the previous one's result. If you branch from one promise, all branches get the same original result.