JavaScript: Promises
Learn how Promises represent future values, how to chain them cleanly, and how to coordinate multiple async operations.
What are Promises?
A Promise is an object that represents the eventual result of an asynchronous operation. Instead of passing a callback into a function, you get a Promise back and attach your handlers to it, making async code far easier to read and chain.
Promise
A Promise is a placeholder for a value that is not available yet but will be at some point in the future.
- Solves callback hell by returning an object you can chain instead of nesting callbacks
- A Promise is always in one of three states: pending, fulfilled, or rejected
- Once settled (fulfilled or rejected) a Promise never changes state again
- Introduced in ES6 as the foundation for async/await
Promise vs Callback
The Promise version returns an object you chain; the callback version nests.
Press Run to execute the code and see output here.
Promise States
Every Promise moves through a strict lifecycle. Understanding the states helps you predict exactly when your then andcatch handlers will run.
Three States
A Promise starts pending and transitions to either fulfilled or rejected exactly once.
- pending: initial state, operation is in progress
- fulfilled: operation succeeded, resolve(value) was called
- rejected: operation failed, reject(reason) was called
- Fulfilled and rejected are collectively called "settled", a settled Promise is immutable
Promise States
Inspect each state: fulfilled resolves through then, rejected through catch.
Press Run to execute the code and see output here.
Creating Promises
Pass an executor function to new Promise(). The executor receives two callbacks: resolve to fulfill and reject to fail the Promise.
new Promise(executor)
The executor runs synchronously; resolve and reject are called asynchronously when the result is known.
- The executor function runs immediately and synchronously
- Call
resolve(value)when the operation succeeds - Call
reject(new Error(...))when it fails, always pass an Error object for a stack trace - Calling both or calling one twice has no effect, first call wins
Creating a Promise
Validate input synchronously, then resolve or reject based on the async result.
Press Run to execute the code and see output here.
then(), catch(), finally()
These three methods attach handlers to a Promise. Each returns a new Promise, enabling chaining.
Promise Handlers
then handles success, catch handles failure, finally runs regardless of the outcome.
then(onFulfilled): runs when the Promise resolves, receives the fulfilled valuecatch(onRejected): runs when the Promise rejects, receives the errorfinally(fn): runs always, ideal for cleanup like hiding a loading spinner- All three return a new Promise, so they can be chained further
then / catch / finally
Chain handlers to process the result, catch any error, and always clean up.
Press Run to execute the code and see output here.
Promise Chaining
Because then() always returns a new Promise, you can chain multiple async steps in a flat sequence instead of nesting them.
Promise Chaining
Returning a value from then() passes it as the input to the next then() in the chain.
- Return a plain value from
then(): it becomes the resolved value of the next step - Return a Promise from
then(): the chain waits for it to settle before continuing - Throw inside
then(): skips all remainingthen()s and jumps tocatch() - One
catch()at the end handles errors from any step in the chain
Promise Chain
Three dependent async steps written as a flat chain instead of nested callbacks.
Press Run to execute the code and see output here.
Error Handling in Promises
A rejection or a thrown error anywhere in a chain propagates down, skipping allthen() handlers, until it reaches acatch().
Promise Error Propagation
Errors travel down the chain automatically, you do not need a check in every then().
- A rejection jumps over all
then()s and lands in the nearestcatch() - After
catch()handles an error, the chain continues normally in the nextthen() - Re-throw inside
catch()if you cannot handle the error there - An unhandled Promise rejection triggers a warning (Node) or a console error (browser)
Error Propagation Through a Chain
The error jumps from step 2 directly to catch, skipping step 3.
Press Run to execute the code and see output here.
Promise.all()
Promise.all() takes an array of Promises and resolves with an array of all their results, but rejects immediately if any one of them rejects.
Promise.all()
Use Promise.all() to run independent async operations in parallel and wait for all of them.
- Resolves when ALL Promises fulfill, result is an array in the same order as the input
- Rejects as soon as ANY Promise rejects, other pending Promises are ignored
- Best for: fetching multiple resources at once when you need all of them to succeed
- If order matters, the result array preserves input order regardless of which resolved first
Promise.all()
Fetch three resources in parallel; one rejection fails the whole batch.
Press Run to execute the code and see output here.
Promise.race()
Promise.race() resolves or rejects as soon as the first Promise in the array settles, the rest are ignored.
Promise.race()
Use Promise.race() to implement timeouts or take the fastest response from multiple sources.
- Settles with the first Promise to settle, whether fulfilled or rejected
- Classic use: pair a real fetch with a timeout Promise to enforce a deadline
- Does not cancel the slower Promises, they keep running in the background
Promise.race() Timeout Pattern
Race the real request against a timeout, whichever settles first wins.
Press Run to execute the code and see output here.
Promise.allSettled()
Promise.allSettled() waits for every Promise to finish and returns a result object for each, whether it fulfilled or rejected. It never rejects itself.
Promise.allSettled()
Use allSettled() when you need the outcome of every operation, even if some fail.
- Each result has a
statusfield:"fulfilled"or"rejected" - Fulfilled results also have a
valuefield - Rejected results also have a
reasonfield - Never rejects, safe to use without a
catch(), though one is still good practice
Promise.allSettled()
Inspect every outcome, partial failures do not hide successful results.
Press Run to execute the code and see output here.
Promise.any()
Promise.any() resolves as soon as the first Promise fulfills, ignoring rejections. It only rejects if every single Promise rejects.
Promise.any()
Use Promise.any() when you have multiple sources and only need one to succeed.
- Resolves with the first fulfilled value, rejections are ignored until all fail
- If all reject, it rejects with an
AggregateErrorcontaining every rejection reason - Opposite of
Promise.all(): any wins vs all must win - Good for: trying multiple CDNs or API mirrors and using whichever responds first
Promise.any()
First fulfillment wins, mirror 1 failing does not matter because mirror 2 succeeds.
Press Run to execute the code and see output here.
Promise.resolve() and Promise.reject()
These shortcuts create an already-settled Promise without writing a fullnew Promise() constructor.
Static Shortcuts
Use Promise.resolve() and Promise.reject() to wrap known values into a Promise quickly.
Promise.resolve(value): returns an already-fulfilled PromisePromise.reject(reason): returns an already-rejected Promise- Useful in tests, stubs, and functions that sometimes return synchronously but must always return a Promise
- If you pass an existing Promise to
Promise.resolve(), it is returned as-is
Promise.resolve() and Promise.reject()
Return a resolved Promise for cached data so callers always get a Promise.
Press Run to execute the code and see output here.
Converting Callbacks to Promises
Older APIs use callbacks. You can wrap them in a Promise to use them in modern async chains or withasync/await.
Promisification
Wrapping a callback-based function in new Promise() is called promisification.
- Create a new Promise whose executor calls the original callback-based function
- In the callback: reject on error, resolve on success
- Node.js provides
util.promisify(fn)to do this automatically for error-first callbacks - Once promisified, the function works with
.then()andasync/await
Promisifying a Callback
Wrap the callback pattern in new Promise so it can be chained or awaited.
Press Run to execute the code and see output here.
| Method | Input | Resolves when | Rejects when |
|---|---|---|---|
Promise.all() | Array of Promises | ALL fulfill | ANY rejects |
Promise.race() | Array of Promises | First to settle (fulfills) | First to settle (rejects) |
Promise.allSettled() | Array of Promises | ALL settle (never rejects) | Never |
Promise.any() | Array of Promises | First to fulfill | ALL reject (AggregateError) |
Knowledge Check
1. Which Promise state means the operation is still in progress?
2. What does the then() method receive as its argument?
3. Where should you place a catch() in a Promise chain to handle errors from any step?
4. What does Promise.all() do when one of the input Promises rejects?
5. Which Promise method returns results for ALL Promises, whether they fulfilled or rejected?
6. What does Promise.race() resolve with?
7. What does Promise.any() do if ALL input Promises reject?