then() in JavaScript: chaining work onto a promise

then() registers the work to run once a promise is fulfilled and returns a new promise: chaining, error handling, and how it compares with await.
3 min read
Believemy logo

A promise does not hand its value back, it hands it over. So it has to be told in advance who receives it, and then is the counter built for that.

Its real strength is not taking the value, it is returning a promise of its own. That one detail turns a run of steps into a readable column.


Definition

then(handler) registers a function to run once the Promise is fulfilled, and returns a new promise carrying the result of that handler. Each link feeds the next one.

JAVASCRIPT
Promise.resolve(2)
  .then((v) => v * 3)                    // 6
  .then((v) => Promise.resolve(v + 1))   // 7, the promise gets unwrapped
  .then((v) => console.log("value", v)); // value 7

The second link returns a promise rather than a number: the chain waits on it and passes its value along, never producing a promise of a promise. A return of a plain value and a return of a promise therefore behave alike.


What happens when something breaks

A throw inside a then skips every following link and goes straight to the first catch it meets. After that, the chain carries on as usual.

JAVASCRIPT
Promise.resolve("record")
  .then(() => { throw new Error("network dropped"); })
  .then(() => console.log("never runs"))
  .catch((err) => console.log("caught:", err.message))
  .then(() => console.log("the chain resumes"));

// caught: network dropped
// the chain resumes

A single catch at the end of the chain therefore covers every step, where a callback style would have demanded an error test at each level.


The second argument, and why it gets avoided

then accepts a second function, called on rejection. It looks like catch without having its reach.

FormWhat it covers
then(ok, fail)A rejection from before, never an error thrown by ok
then(ok).catch(fail)A rejection from before and an error thrown by ok

Those two columns explain the general preference for catch: the second argument lets through the error raised by the very handler it accompanies, which produces an unhandled rejection that is hard to track down.

Good to know

then never runs within the current pass, not even on an already-fulfilled promise. The handler goes into the microtask queue and waits for the running code to finish, which guarantees a stable order whatever the speed of the operation.


Frequently asked questions

Question

Should you prefer then or await?

await wins as soon as there are several dependent steps or any conditional logic, because the code becomes linear again. then stays shorter for a single transformation, and it is the only option outside an async function in older environments.


Question

Why does my then receive undefined?

Because the previous link returned nothing. An arrow function with braces needs an explicit return: (v) => { v * 2 } hands back nothing, unlike (v) => v * 2. It is the single most common oversight in a chain.


Question

Can several then calls sit on the same promise?

Yes, and it is not the same thing as chaining. Two then calls placed on the same variable both receive the original value and create two independent branches, whereas chaining walks the value from link to link.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.