Skip to main content

JavaScript Asynchronous

JavaScript Promise Chaining

Written by Published

Returning a value from then passes it to the next then in the chain.

Each then returns a new promise, which is what makes chaining possible.

This is how promises replace deeply nested callbacks with a flat sequence.

Example

Example

javascript

Promise.resolve(2)
  .then(function (n) {
    return n * 5;
  })
  .then(function (n) {
    console.log(n);
  });

The output is 10.

How Chaining Works

then returns a new promise.

Whatever you return from a handler becomes the value of that promise.

Returning a promise makes the chain wait for it.

Syntax

Syntax

javascript

promise
  .then(stepOne)
  .then(stepTwo)
  .catch(handleError);

A single catch at the end covers the whole chain.

Passing Values Along

Each step receives what the one before it returned.

Example

Example

javascript

Promise.resolve("a")
  .then(function (value) {
    return value + "b";
  })
  .then(function (value) {
    return value + "c";
  })
  .then(function (value) {
    console.log(value);
  });

The output is abc.

Waiting for Another Promise

If a handler returns a promise, the next step waits for it to settle.

This is what makes sequential asynchronous work readable.

Example

Example

javascript

function delay(value) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(value);
    }, 10);
  });
}

delay("one")
  .then(function (a) {
    return delay(a + " two");
  })
  .then(function (value) {
    console.log(value);
  });

Compare this with the nested callback version: same work, flat shape.

The Missing return

Forgetting to return breaks the chain, and the next step gets undefined.

This is the single most common promise bug.

Example

Example

javascript

Promise.resolve(2)
  .then(function (n) {
    return n * 5;
  })
  .then(function (n) {
    console.log(n === undefined ? "lost the value" : n);
  });

Without the return the output would be lost the value.

One catch for the Whole Chain

An error at any step skips straight to the catch.

Example

Example

javascript

Promise.resolve(1)
  .then(function () {
    throw new Error("step two failed");
  })
  .then(function () {
    console.log("this is skipped");
  })
  .catch(function (error) {
    console.log("caught: " + error.message);
  });

The output is caught: step two failed.

Carrying On After a catch

A chain recovers after a catch, so later steps still run.

Example

Example

javascript

Promise.reject("broken")
  .catch(function () {
    return "recovered";
  })
  .then(function (value) {
    console.log(value);
  });

The output is recovered.

Complete Example

Complete Example

html

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript Promise Chaining</title>
</head>
<body>

  <h1>Promise Chaining</h1>

  <p id="out">Working...</p>

  <script>
    function delay(value, ms) {
      return new Promise(function (resolve) {
        setTimeout(function () {
          resolve(value);
        }, ms);
      });
    }

    delay("Step 1", 500)
      .then(function (value) {
        document.getElementById("out").textContent = value;
        return delay("Step 2", 500);
      })
      .then(function (value) {
        document.getElementById("out").textContent = value + " — done";
      })
      .catch(function (error) {
        document.getElementById("out").textContent = "Failed: " + error;
      });
  </script>

</body>
</html>

Try It Yourself

Run the above example in the Try It Editor.

Try removing a return:

Delete the return before the second delay and watch the timing break.

Important Points

  • then returns a new promise, so calls can be chained.
  • What a handler returns becomes the next step's value.
  • Returning a promise makes the chain wait for it.
  • Forgetting return passes undefined on.
  • One catch at the end covers every step.

Conclusion

Chaining is what turns nested callbacks into a readable sequence.

The rule to remember is simple: always return from a handler.

async and await make this flatter still.