JavaScript Promise Table
| API | Returns | What it does |
|---|---|---|
new Promise(fn) | Promise | fn gets (resolve, reject) - the state machine starts pending |
.then(onOK) | Promise | Runs on fulfillment; returns a new promise for chaining |
.then(ok, onErr) | Promise | Two-argument form - error handling that does not catch downstream |
.catch(onErr) | Promise | Catches rejection anywhere above it in the chain |
.finally(fn) | Promise | Runs either way - hide the spinner here, no arguments received |
Promise.all([...]) | Promise | All must fulfill; first rejection rejects the whole batch |
Promise.allSettled([...]) | Promise | Waits for every outcome - never rejects; inspect each status |
Promise.race([...]) | Promise | First settled wins - fulfillment or rejection alike; timeout tricks live here |
Promise.any([...]) | Promise | First fulfillment wins; rejects only if all fail (AggregateError) |
Promise.resolve(v) | Promise | Wraps a plain value (or thenable) into a fulfilled promise |
Promise.reject(err) | Promise | Rejected promise from the start |
async / await | Syntax sugar | await pauses inside async functions; try/catch replaces .catch |
Chaining rule | - | Each .then receives the previous return value - return inside then or the chain loses the data |
A promise is a small state machine: it starts pending, then settles exactly once - fulfilled with a value or rejected with a reason. Every method on the table below is a different way to join that outcome. The instance methods (then, catch, finally) handle one promise; the static combinators (all, allSettled, race, any) coordinate a whole batch.
Bottom line: the combinator is the real decision, and the four map to four intents. all = everything or nothing (a dashboard that needs every dataset). allSettled = report on everything (batch uploads where partial success matters). race = first answer wins (timeout patterns). any = first success wins (redundant mirrors). Pick by intent and the error handling writes itself.
The honest part: async/await is not a third thing - it is syntax over this exact machinery. await rejects by throwing, which is why try/catch replaces .catch; forgetting that mapping is how unhandled-rejection warnings are born. And the chaining rule hides in plain sight: each .then receives the PREVIOUS handler's return value - forget to return inside a then and the next link quietly gets undefined.
How to use
- Find the intent in the first column - instance methods for one promise, static combinators for a batch.
- Check the Returns column: everything returns a new promise, which is why chains work and why a non-returned value breaks one.
- Map to async/await when reading: then is sequential awaits, catch is try/catch, finally is the code after the try block.
Frequently asked questions
What is the difference between Promise.all and Promise.allSettled?
all is all-or-nothing: if any input rejects, the whole call rejects immediately with that first reason, and you learn nothing about the others (they keep running in the background, unhanded). allSettled waits for every promise to finish, then reports an array of {status, value/reason} - never rejects. Fetching five datasets for one screen: all. Uploading ten files where partial success is acceptable: allSettled, then retry the failures by name.
How do async/await and .then() map to each other?
await p is sugar for p.then(v => ...): it pauses the async function until p settles. A rejection under await becomes a thrown exception, so try { await fetch... } catch (e) { ... } replaces .catch. Sequential awaits replace chained .then calls; running things in parallel still needs the combinators - const [a, b] = await Promise.all([p1, p2]) - because awaiting in sequence serializes work that could overlap.
What does Promise.race actually do on a rejection?
It settles with the first settled promise, period - fulfillment or rejection, whichever lands first. That makes race the standard timeout idiom (race(fetch, rejectAfter(5000))) and also its own footgun: if the loser of the race rejects later, that rejection is still unhandled. Production timeout wrappers attach a no-op catch to the losing promise. any is the optimistic sibling: it ignores rejections entirely and settles with the first fulfillment, rejecting only when every input fails.
Why does my .then chain pass undefined to the next step?
Because the previous handler did not return. Each .then receives the return value of the one before it - a handler that calls a function without returning its result feeds undefined downstream. The silent version of the same bug: creating a promise inside a then and forgetting to return it, so the chain no longer waits for that work. The rule: every then either returns a value or returns a promise, and review chains specifically for missing returns.