A Promise represents a value that may not be available yet, in one of three states: pending, fulfilled, or rejected — and once settled (fulfilled or rejected), it never changes state again.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) resolve("done!");
else reject(new Error("failed"));
}, 1000);
});
promise
.then((value) => console.log(value)) // runs only on fulfillment
.catch((error) => console.error(error)) // runs only on rejection
.finally(() => console.log("always runs, regardless of outcome"));
Each .then() returns a new Promise, which is what makes chaining work — and a thrown error or rejection skips forward past any .then()s to the next .catch():
fetchUser(id)
.then((user) => fetchPosts(user.id)) // returns a new Promise, chained onto
.then((posts) => posts[0])
.catch((err) => console.error("failed at some step:", err)); // catches a rejection from ANY step above
| Method | Resolves when | Rejects when |
|---|---|---|
Promise.all([...]) | Every Promise fulfills — result is an array of values, in order | Any single Promise rejects — immediately, discarding the others' results |
Promise.allSettled([...]) | Every Promise settles, fulfilled or rejected — never itself rejects | Never — each result reports its own status |
Promise.race([...]) | The first Promise to settle, fulfilled or rejected | Same — whichever settles first, win or lose |
Promise.any([...]) | The first Promise to fulfill | Only if all of them reject |
const [user, posts] = await Promise.all([fetchUser(id), fetchPosts(id)]);
// runs both requests concurrently rather than one after another — much faster than two sequential awaits
Promise.all failing fast (on the very first rejection) is exactly why Promise.allSettled exists — for cases like "send five independent notifications, and report which ones failed" where one failure shouldn't discard the results of the four that succeeded.