Promise.prototype.then()
Registers callbacks for a promise's fulfillment and rejection, returning a new chained promise.
then(onFulfilled)
then(onFulfilled, onRejected)- Added in JavaScript ES2015
Parameters
| Parameter | Type | Description |
|---|---|---|
$onFulfilledoptional | Function | Called with the fulfillment value when the promise fulfills. If it is not a function, it is replaced with an identity function (x) => x, passing the value straight through. |
$onRejectedoptional | Function | Called with the rejection reason when the promise rejects. If it is not a function, it is replaced with a thrower (x) => { throw x; }, so the rejection propagates down the chain. |
Return value
PromiseA new Promise, always pending when returned. It fulfills with the handler's return value, rejects with anything the handler throws, or adopts the state of a promise the handler returns.
Errors
then() itself does not throw; an error thrown inside a handler rejects the promise it returns.
then() is the primitive every other promise method is built on. It schedules your handler as a microtask — never synchronously, even on an already-fulfilled promise — and hands back a new promise so calls can be chained.
How the chain propagates
Whatever a handler returns becomes the fulfillment value of the next link. Return a promise and the chain waits for it and unwraps it, one layer deep or many. Throw and the next link rejects. Return nothing and the next link fulfills with undefined — the single most common cause of a mysteriously empty value halfway down a chain.
When to reach for it
Prefer await inside an async function for readability. Reach for then() when you are outside an async context, when you want to attach a handler without pausing (fire-and-forget logging), or when you are building a pipeline programmatically, for example with reduce.
What trips people up
The two-argument form then(onFulfilled, onRejected) does not catch errors thrown by onFulfilled — the two handlers are alternatives for the same promise, not a try/catch pair. Chain a separate .catch() afterwards if you need to cover both.
Example
When to use it
- Transform a resolved value before it reaches the rest of your code
- Kick off a follow-up request that depends on the first one's result
- Attach logging to a promise without awaiting it
- Build a chain dynamically from an array of steps
Frequently asked questions
What is the difference between .then() and await?
They do the same unwrapping; await just pauses the surrounding async function instead of registering a callback, so the code reads top to bottom. Use await inside async functions and then() when you are at the top level of a non-module script or want to attach a handler without pausing.
Why does my then() handler receive undefined?
Because the previous handler did not return anything — a handler with no return statement fulfills the next promise with undefined. Watch for arrow functions with braces, where { doThing() } returns nothing while doThing() returns the value.
Do then() callbacks run synchronously if the promise is already resolved?
No, never. Handlers are always queued as microtasks and run after the current synchronous code finishes, which is why a console.log placed after the chain prints first.
More examples
Both handlers on one then()
The first two calls show onFulfilled and onRejected as alternatives. The third shows why they are not a try/catch: the throw inside onFulfilled is only picked up by the next link in the chain, not by the onRejected sitting beside it.
const settle = (ok) =>
ok ? Promise.resolve("ok") : Promise.reject(new Error("bad"));
await settle(true).then(
(v) => console.log("fulfilled:", v),
(e) => console.log("rejected:", e.message),
);
await settle(false).then(
(v) => console.log("fulfilled:", v),
(e) => console.log("rejected:", e.message),
);
await settle(true)
.then(() => { throw new Error("thrown in handler"); })
.then(null, (e) => console.log("second handler saw:", e.message));Return values, thrown errors, and non-function handlers
Returned values accumulate down the chain ("abc"). A returned rejected promise is adopted, so the next link's onRejected runs. Passing a non-function is not an error — it is replaced by an identity function, so the original value "a" passes through untouched.
const p = Promise.resolve("a");
console.log(await p.then((v) => v + "b").then((v) => v + "c"));
const chained = p.then(() => Promise.reject(new Error("inner")));
console.log(await chained.then(() => "never", (e) => "recovered from " + e.message));
console.log(await p.then("not a function"));
Discussion