Every modern async API — fetch, file system, databases — returns promises.
A promise is an object standing for a value that will be available later. It is in one of three states: pending, then either fulfilled with a value or rejected with a reason (usually an Error). Once settled, it never changes.
You react with methods that each return a new promise, which is what makes chaining work:
.then(onFulfilled)— receives the value. Whatever you return becomes the next promise's value; if you return a promise, the chain waits for it..catch(onRejected)— handles a rejection from any earlier step. Throwing inside athenrejects the chain too..finally(fn)— runs either way, for cleanup.
loadUser(id)
.then(user => loadOrders(user.id))
.then(orders => render(orders))
.catch(showError);Always return from then callbacks when you start more async work; forgetting return breaks the chain and the next step runs too early. Always end a chain with catch (or await it in a try): an unhandled rejection is reported as an error, and Node.js exits by default.
Combinators run promises concurrently:
Promise.all(ps)— fulfils with an array of all values; rejects as soon as any rejects.Promise.allSettled(ps)— waits for all, giving{ status, value | reason }for each.Promise.race(ps)— settles like the first one to settle.Promise.any(ps)— the first to fulfil; rejects only if all reject.
To wrap a callback API, use the constructor: new Promise((resolve, reject) => ...). Promise.withResolvers() (ES2024) returns { promise, resolve, reject } when you need to resolve from outside. Promise.resolve(x) and Promise.reject(err) create already-settled promises.
Example
function delay(ms, value) {
return new Promise(resolve => setTimeout(() => resolve(value), ms));
}
function failAfter(ms) {
return new Promise((_, reject) => setTimeout(() => reject(new Error("boom")), ms));
}
delay(20, 2)
.then(n => n * 10) // plain value
.then(n => delay(20, n + 1)) // returned promise is awaited
.then(n => console.log("chain:", n)) // chain: 21
.catch(err => console.error(err))
.finally(() => console.log("chain done"));
Promise.all([delay(10, "a"), delay(30, "b")])
.then(values => console.log("all:", values)); // all: [ 'a', 'b' ]
Promise.allSettled([delay(10, "ok"), failAfter(5)])
.then(results => console.log(results.map(r => r.status))); // [ 'fulfilled', 'rejected' ]
Promise.any([failAfter(5), delay(15, "first success")])
.then(v => console.log("any:", v)); // any: first successRemember
- A promise is pending, then fulfilled or rejected — once
thenreturns a new promise; return values (or promises) to chainallfails fast,allSettledwaits for everything,anywants one success
Watch out for
- Forgetting to
returna promise inside.then, so the next step doesn't wait. - Leaving a chain without
.catch, producing unhandled rejections.