JavaScript Promises
Handle async code with promises, then, catch, async/await, and Promise combinators like all.
TL;DR
- 01Promises represent values that arrive later from async work.
- 02Chain handlers with
then,catch, andfinallymethods. - 03Use
asyncandawaitfor cleaner, synchronous-looking async code.
Tips
- 01Use <code>Promise.all()</code> for independent async work so every request runs in parallel instead of waiting one at a time.
- 02Use <code>Promise.allSettled()</code> when you need the outcome of every promise, including failures, instead of stopping at the first rejection.
Warnings
- 01A single rejected promise inside <code>Promise.all()</code> rejects the whole call, so use <code>Promise.allSettled()</code> when partial failure is acceptable.
- 02Forgetting to <code>return</code> a promise inside <code>.then()</code> breaks the chain, since the next handler runs before that nested work finishes.
Creating Promises
- Every promise locks into one of three states for life, and you control the transition yourself with
new Promiseand an executor that calls resolve or reject.const p = new Promise((resolve, reject) => { if (ok) resolve(value); else reject(error); }); - Use
Promise.resolve()to wrap any value in an already-fulfilled promise.Promise.resolve(42).then(v => console.log(v)); - Use
Promise.reject()to create an already-rejected promise for testing.Promise.reject('Error!').catch(e => console.log(e)); - Promises have three states: pending, fulfilled, or rejected, and never change back.
- Most APIs you use, like
fetch, already return promises for you to consume.
Handling Results
- A promise is useless until something reacts to it — use
.then()to run code when the promise fulfills with a value.promise.then(result => console.log(result)); - Use
.catch()to handle any error in the promise or in a.thencallback.promise.catch(error => console.error(error)); - Use
.finally()to run cleanup code regardless of success or failure.promise.finally(() => console.log('Done')); - Each
.then()returns a new promise, which lets you chain steps together. - Always include a
.catch()or try/catch — unhandled rejections cause warnings.
Promise Chaining
- What turns a single async call into a readable pipeline? Each
.then()returns a new promise carrying the return value of its callback.fetch('/api/users/1') .then(res => res.json()) .then(user => console.log(user.name)) .catch(err => console.error(err)); - A
.catch()anywhere in a chain catches errors from all preceding steps. - Return a new promise inside
.then()to wait for it before the next step.getUser(id) .then(user => getOrders(user.id)) .then(orders => console.log(orders)); - A
.catch()followed by.then()lets the chain recover and continue.fetch('/api/data') .catch(() => getCachedData()) .then(data => render(data)); - Avoid nesting
.then()inside another.then()— flatten the chain instead to avoid promise hell.
Combining Promises
- Four combinators, four different failure behaviors — pick wrong and one slow request can stall your whole UI. Use
Promise.all()to wait for all promises and get an array of results.const [a, b] = await Promise.all([p1, p2]); - Use
Promise.race()to settle as soon as the first promise finishes.Promise.race([fast, slow]).then(r => console.log(r)); - Use
Promise.any()to get the first fulfilled value, ignoring rejections.Promise.any([p1, p2]).then(r => console.log(r)); - Use
Promise.allSettled()when you want every result, success or failure.Promise.allSettled([p1, p2]).then(results => console.log(results)); Promise.all()rejects fast if any promise fails, so preferallSettledfor bulk work.
Common Patterns
- Sequential awaits are the most common performance bug in async code — run promises one after another using
awaiton each call only when each step truly depends on the last.const a = await first(); const b = await second(); - Run promises in parallel with
Promise.allfor faster completion.const [a, b] = await Promise.all([first(), second()]); - Wrap callbacks like
setTimeoutin a promise for use with await.const delay = ms => new Promise(r => setTimeout(r, ms)); - Use
fetchto make HTTP requests, thenawait response.json()for parsing.const res = await fetch('/api/users/1'); const user = await res.json(); - Choose sequential when steps depend on each other and parallel when they do not.
FAQ
Use new Promise((resolve, reject) => { ... }), calling resolve(value) on success and reject(error) on failure. In Node.js, util.promisify automates this for any function that follows the standard (err, result) callback convention.
Each await in sequence blocks until the previous call finishes, so total time is the sum of all durations. Kick off independent operations simultaneously by passing them to Promise.all() instead of awaiting each one on its own line.
Rejections silently disappear in older environments, and in Node.js v15+ they crash the process with an unhandled rejection error. Always terminate every chain with .catch(), or wrap await calls in a try/catch block.
Promise.race() settles as soon as the first promise settles — resolved or rejected. Promise.any() waits for the first promise to resolve successfully and ignores individual rejections. If every promise rejects, Promise.any() throws an AggregateError.
The callback passed to forEach becomes async, but forEach itself never awaits those callbacks. Your loop completes before any async work finishes. Use a for...of loop with await for sequential execution, or Promise.all(array.map(...)) for parallel execution.