The then
method returns a new Promise
, which allows for method chaining.
If the function passed as handler to then
returns a Promise
, an equivalent Promise
will be exposed to the subsequent then
in the method chain. The below snippet simulates asynchronous code with the setTimeout
function.
Promise.resolve("foo")
.then(
(string) =>
new Promise((resolve, reject) => {
setTimeout(() => {
string += "bar";
resolve(string);
}, 1);
}),
)
.then((string) => {
setTimeout(() => {
string += "baz";
console.log(string);
}, 1);
return string;
})
.then((string) => {
console.log(
"Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising",
);
console.log(string);
});
The value returned from then()
is resolved in the same way as Promise.resolve()
. This means thenable objects are supported, and if the return value is not a promise, it's implicitly wrapped in a Promise
and then resolved.
const p2 = new Promise((resolve, reject) => {
resolve(1);
});
p2.then((value) => {
console.log(value);
return value + 1;
}).then((value) => {
console.log(value, "- A synchronous value works");
});
p2.then((value) => {
console.log(value);
});
A then
call returns a promise that eventually rejects if the function throws an error or returns a rejected Promise.
Promise.resolve()
.then(() => {
throw new Error("Oh no!");
})
.then(
() => {
console.log("Not called.");
},
(error) => {
console.error(`onRejected function called: ${error.message}`);
},
);
In practice, it is often desirable to catch()
rejected promises rather than then()
's two-case syntax, as demonstrated below.
Promise.resolve()
.then(() => {
throw new Error("Oh no!");
})
.catch((error) => {
console.error(`onRejected function called: ${error.message}`);
})
.then(() => {
console.log("I am always called even if the prior then's promise rejects");
});
In all other cases, the returned promise eventually fulfills. In the following example, the first then()
returns 42
wrapped in a fulfilled Promise, even though the previous Promise in the chain was rejected.
Promise.reject()
.then(
() => 99,
() => 42,
)
.then((solution) => console.log(`Resolved with ${solution}`));
If onFulfilled
returns a promise, the return value of then
will be fulfilled/rejected based on the eventual state of that promise.
function resolveLater(resolve, reject) {
setTimeout(() => {
resolve(10);
}, 1000);
}
function rejectLater(resolve, reject) {
setTimeout(() => {
reject(new Error("Error"));
}, 1000);
}
const p1 = Promise.resolve("foo");
const p2 = p1.then(() => {
return new Promise(resolveLater);
});
p2.then(
(v) => {
console.log("resolved", v);
},
(e) => {
console.error("rejected", e);
},
);
const p3 = p1.then(() => {
return new Promise(rejectLater);
});
p3.then(
(v) => {
console.log("resolved", v);
},
(e) => {
console.error("rejected", e);
},
);
You can use chaining to implement one function with a Promise-based API on top of another such function.
function fetchCurrentData() {
return fetch("current-data.json").then((response) => {
if (response.headers.get("content-type") !== "application/json") {
throw new TypeError();
}
const j = response.json();
return j;
});
}