Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

JavaScript Promises

Understand Promise states, resolution, chaining, rejection propagation, cleanup, combinators, thenable assimilation, and modern Promise APIs.

Levels: Intermediate ~40 min read

Intermediate

A Promise is an ECMAScript object representing the eventual result of an operation. The important skill is not memorizing promise methods; it is tracing settlement, chaining, error propagation, and concurrency. This lesson builds that mental model before introducing the Promise combinators and the newer Promise.withResolvers() and Promise.try() APIs.

A Promise has a state, and settlement is one-way

A promise starts pending. It becomes fulfilled with a value or rejected with a reason. Fulfilled and rejected are both settled states. A promise can be resolved to another promise or thenable, which means “resolved” is not always the same thing as “already fulfilled.”

Interactive Runnable example
Edit

Ready to run.

Quick check

Which pair are the two settled Promise states?

const settled = Promise.resolve(42);

Creating a Promise separates production from observation

The Promise constructor receives an executor. The executor is called during construction, and it receives resolve and reject functions. Calling resolve or reject settles the promise, subject to the promise resolution procedure. The constructor itself is synchronous; the handlers attached with then/catch run later as promise jobs.

Interactive Runnable example
Edit

Ready to run.

Quick check

What runs synchronously when new Promise(executor) is evaluated?

new Promise(resolve => resolve(1));

Resolution can adopt another Promise

Calling resolve does not necessarily mean “store this value immediately.” If the resolution value is a promise or thenable, the new promise follows that value. This is why Promise resolution is a useful conceptual layer distinct from the final fulfilled/rejected state.

Interactive Runnable example
Edit

Ready to run.

Quick check

What happens when a Promise is resolved with another Promise?

Promise.resolve(Promise.resolve(3)).then(console.log);

then creates a new Promise and transforms outcomes

then does not mutate the original promise into a new value. It returns a new promise. If the fulfillment handler returns a normal value, the new promise fulfills with that value. If the handler returns a promise or thenable, the new promise adopts it. If the handler throws, the new promise rejects.

Interactive Runnable example
Edit

Ready to run.

Quick check

What does a then callback returning 10 do to the next Promise in the chain?

Promise.resolve(1).then(() => 10).then(console.log);

Returning a Promise flattens the chain

A then handler can return another Promise. The promise returned by then adopts that result rather than producing a nested Promise that you must manually unwrap. Thenable assimilation extends the same idea to objects with a callable then method.

Interactive Runnable example
Edit

Ready to run.

Quick check

Why can a then handler return a Promise without creating Promise<Promise<T>> in normal use?

Promise.resolve(1).then(() => Promise.resolve(2));

Thrown errors become rejections in the returned chain

If a fulfillment or rejection handler throws, the Promise returned by then rejects with that thrown value. This gives Promise chains a structured error path: synchronous exceptions inside handlers become asynchronous rejection outcomes for the next rejection handler.

Interactive Runnable example
Edit

Ready to run.

Quick check

If a then handler throws, what happens to the Promise returned by then?

Promise.resolve().then(() => { throw new Error("x"); }).catch(console.log);

catch is rejection handling through then

catch(onRejected) is equivalent in behavior to attaching a rejection handler with then(undefined, onRejected). It handles rejection from the preceding chain and returns another Promise, so the handler can recover with a value or continue by throwing/returning a rejection.

Interactive Runnable example
Edit

Ready to run.

Quick check

What can a catch handler return to recover a chain?

Promise.reject("x").catch(() => "fallback");

finally observes completion without replacing the result by default

Promise.prototype.finally runs after the preceding promise is fulfilled or rejected. Its normal completion lets the original outcome continue unchanged. If finally itself throws or returns a rejected Promise, that new failure replaces the earlier outcome.

Interactive Runnable example
Edit

Ready to run.

Quick check

What normally happens when a finally callback completes normally?

Promise.resolve(7).finally(() => {}).then(console.log);

Promise.resolve normalizes values and thenables

Promise.resolve(x) returns a fulfilled Promise for an ordinary value, but it adopts an existing Promise or compatible thenable. This makes it useful when an API accepts either synchronous values or promise-like results and you want one Promise-based interface.

Interactive Runnable example
Edit

Ready to run.

Quick check

What is a practical use of Promise.resolve?

Promise.resolve(maybePromise).then(useValue);

Promise.all is for all-or-fail aggregation

Promise.all waits for all input elements to fulfill and then fulfills with an array of values in input order. If any input rejects, the returned Promise rejects with that rejection. “First rejection” describes the aggregate rejection outcome, not cancellation of the other underlying operations.

Interactive Runnable example
Edit

Ready to run.

Quick check

What order does Promise.all use for its fulfillment array?

Promise.all([slow(), fast()]).then(([slowValue, fastValue]) => {});

Promise.allSettled reports every outcome

Promise.allSettled fulfills after every input has settled. Each result records either status fulfilled with a value or status rejected with a reason. It is appropriate when you need a complete report rather than failing the aggregate at the first rejection.

Interactive Runnable example
Edit

Ready to run.

Quick check

When is Promise.allSettled useful?

Promise.allSettled(tasks).then(reportAllResults);

Promise.race settles with the first settlement

Promise.race returns a Promise that settles as soon as the first input settles, whether that input fulfills or rejects. It does not itself cancel the slower operations. Cancellation is a separate concern provided by appropriate APIs in the host environment.

Interactive Runnable example
Edit

Ready to run.

Quick check

What does Promise.race care about?

Promise.race([requestA, requestB]).then(use).catch(handle);

Promise.any waits for the first fulfillment

Promise.any fulfills when the first input fulfills. Rejections are ignored while other inputs remain possible winners. If every input rejects, the aggregate rejects with an AggregateError containing the rejection reasons.

Interactive Runnable example
Edit

Ready to run.

Quick check

When does Promise.any reject?

Promise.any([primary(), backup()]).catch(handleAllFailures);

withResolvers separates capability creation from executor code

Promise.withResolvers() returns an object containing a new promise plus its associated resolve and reject functions. This can simplify APIs that need to expose or store the settlement controls separately from the code that creates the Promise. It does not make settlement multi-use: the Promise still settles only once.

Interactive Runnable example
Edit

Ready to run.

Quick check

What does Promise.withResolvers return?

const { promise, resolve, reject } = Promise.withResolvers();

Promise.try converts synchronous calls into Promise outcomes

Promise.try(callback, ...args) calls the callback and turns either a normal return value or a thrown exception into a Promise outcome. It is useful when an API wants one Promise-based path for work that may throw synchronously or may already return a Promise.

Interactive Runnable example
Edit

Ready to run.

Quick check

What problem does Promise.try address?

Promise.try(() => mayThrow()).catch(handle);

Thenables are assimilated deliberately

A thenable is an object with a callable then property. Promise resolution treats such an object as a promise-like source and adopts its eventual outcome. Do not treat “has a then method” as proof that an object is a native Promise; Promise resolution is the interoperability boundary.

Interactive Runnable example
Edit

Ready to run.

Quick check

What does Promise.resolve do with a compatible thenable?

Promise.resolve({ then(resolve) { resolve(1); } }).then(console.log);

Choose the combinator from the result you need

Use all when every operation must succeed together. Use allSettled when you need a complete report. Use race when the first settlement determines the result. Use any when the first successful result is enough and all failures matter only if every candidate fails. None of these APIs automatically cancels the underlying operations.

Authoring rules

Trace promise chains by following the Promise returned from each then/catch/finally. Return values fulfill the next Promise; thrown errors reject it; returned thenables are adopted. Keep independent work concurrent when appropriate instead of awaiting each operation unnecessarily. Pick all, allSettled, race, or any based on the outcome contract. Preserve errors rather than converting failures into misleading defaults. Remember that Promise combinators coordinate outcomes; they do not automatically cancel the underlying work.

Practice: design a Promise pipeline

Build a small data pipeline that starts two independent asynchronous operations, combines them with Promise.all, transforms the result with then, handles failures with catch, and performs cleanup in finally. Then rewrite the combination using allSettled and explain what information changes. Try a race and any variant and state exactly what event determines each result.

Specification references

Primary authority: ECMA-262, current ECMAScript specification. Promise objects and algorithms are in the control-abstraction-objects section. Relevant standardized APIs include Promise.resolve/reject, then/catch/finally, all, allSettled, race, any, withResolvers, and try. Current specification: https://tc39.es/ecma262/multipage/ .

Was this page helpful?

Mark this learning resource complete to track your learning progress.