Promises
Promises
JavaScript is single-threaded, so long-running operations (network requests, file I/O, timers) are handled asynchronously. Promises are the foundational abstraction for managing asynchronous results.
What Is a Promise?
A Promise is an object that represents an operation that hasn't completed yet but is expected to in the future. It can be in one of three states:
- Pending — initial state, neither fulfilled nor rejected
- Fulfilled — the operation completed successfully (value available)
- Rejected — the operation failed (reason available)
Once a Promise settles (fulfills or rejects), it cannot change state.
Creating a Promise
const p = new Promise((resolve, reject) => {
// async work...
if (success) resolve(value);
else reject(new Error("Something went wrong"));
});.then / .catch / .finally
Handlers are chained off a Promise:
p
.then((value) => console.log(value))
.catch((err) => console.error(err.message))
.finally(() => console.log("Done"));.then can take two arguments: onFulfilled and onRejected. Returning a value from .then wraps it in a new Promise, enabling chaining.
Promise Combinators
| Method | Description |
|---|---|
Promise.all(arr) | Resolves when all resolve; rejects as soon as any rejects |
Promise.race(arr) | Settles with the first Promise to settle (either way) |
Promise.allSettled(arr) | Waits for all to settle; never rejects; returns status/value pairs |
Promise.any(arr) | Resolves with the first fulfilled Promise; rejects if all reject |
Rejection Handling
Unhandled Promise rejections crash Node.js processes and show warnings in browsers. Always attach a .catch or handle rejection in the second argument of .then.
You can also listen globally: process.on("unhandledRejection", handler) in Node.js.
Code Examples
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "Alice", role: "admin" });
} else {
reject(new Error("Invalid user ID"));
}
}, 50);
});
}
function fetchPermissions(user) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(user.role === "admin" ? ["read", "write", "delete"] : ["read"]);
}, 50);
});
}
// Chain: fetch user, then permissions
fetchUser(1)
.then((user) => {
console.log("User:", user.name);
return fetchPermissions(user); // returning a Promise flattens the chain
})
.then((perms) => {
console.log("Permissions:", perms);
})
.catch((err) => {
console.error("Error:", err.message);
})
.finally(() => {
console.log("Request complete");
});Returning a Promise from .then flattens the chain — the next .then waits for the inner Promise to resolve. .catch at the end catches any rejection from any step in the chain.
const delay = (ms, val) => new Promise((res) => setTimeout(() => res(val), ms));
const fail = (ms, msg) => new Promise((_, rej) => setTimeout(() => rej(new Error(msg)), ms));
// Promise.all — all must succeed
Promise.all([delay(50, "A"), delay(30, "B"), delay(70, "C")])
.then((results) => console.log("all:", results))
.catch((e) => console.error("all failed:", e.message));
// Promise.allSettled — get every outcome
Promise.allSettled([
delay(40, "ok"),
fail(20, "bad request"),
delay(60, "also ok"),
]).then((results) => {
results.forEach(({ status, value, reason }) => {
if (status === "fulfilled") console.log("OK:", value);
else console.log("ERR:", reason.message);
});
});Promise.all is best when all results are needed and any failure means the whole operation fails. Promise.allSettled is ideal for independent operations where you want each result regardless of success or failure.
const delay = (ms, val) => new Promise((res) => setTimeout(() => res(val), ms));
const fail = (ms, msg) => new Promise((_, rej) => setTimeout(() => rej(new Error(msg)), ms));
// Promise.race — first to settle wins (even if rejected)
Promise.race([
delay(100, "slow"),
delay(30, "fast"),
delay(60, "medium"),
]).then((v) => console.log("race winner:", v));
// Promise.any — first to FULFILL wins; ignores rejections
Promise.any([
fail(10, "error 1"),
delay(50, "second"),
fail(20, "error 2"),
]).then((v) => console.log("any winner:", v));
// Promise.any when all reject
Promise.any([
fail(10, "e1"),
fail(20, "e2"),
]).catch((e) => console.log("AggregateError:", e.constructor.name));Promise.race returns the first settled result — useful for timeouts. Promise.any returns the first fulfilled result, skipping rejections — useful for redundant sources. If all reject, it throws an AggregateError.
Quick Quiz
1. What are the three possible states of a Promise?
2. What does Promise.all do if one of the Promises rejects?
3. Which combinator never rejects and always waits for every Promise to settle?
Was this lesson helpful?