Skip to content
Intermediate10 min study

Promises

Compose asynchronous outcomes with settlement states, chaining, error propagation, combinators, and cancellation-aware design.

Question progress0 / 10 completed
Start the lesson
Promises visual explanation

WHAT YOU WILL BE ABLE TO DO

Learning outcomes

  • Explain promises in plain language.
  • Connect the behavior to the underlying browser or framework model.
  • Implement the core pattern and reason through edge cases.
  • Answer common follow-ups without relying on memorized phrases.

01Overview

A promise represents the eventual result of an asynchronous operation. It starts pending and settles exactly once, to either fulfilled (with a value) or rejected (with a reason) — it never settles twice or changes state after settling.

02Mental model

.then registers callbacks and returns a new promise, so chains compose. A rejection skips subsequent .then handlers until a .catch (or a rejection handler passed to .then) intercepts it — unhandled rejections propagate silently otherwise.

Think of it like this: a promise is like a food-delivery tracking number — you get it immediately when you place the order (pending), and at some point it resolves to either 'delivered' (fulfilled) or 'order cancelled' (rejected) — but it only ever settles once, and it never flips back and forth.

03Examples

  • Promise.all: rejects as soon as any input rejects; use when every result is required.
  • Promise.allSettled: always resolves, with per-item status; use when partial success is acceptable.
  • Promise.race: settles as soon as the first input settles (fulfilled or rejected); use for timeouts.
  • Promise.any: fulfills as soon as the first input fulfills, rejecting only if all reject.

04Check understanding

You're loading three independent widgets and want to render whichever succeed even if one fails. Which combinator fits?

Promise.allSettled — it never short-circuits on a single rejection, so you can render the fulfilled widgets and show a fallback for the rejected one.

How to say it out loud: "A promise represents the eventual result of an asynchronous operation. It starts in a pending state and settles exactly once, either to fulfilled with a value or rejected with a reason — it can never settle twice or change state afterward. Calling .then() registers callbacks and returns a brand-new promise, which is what lets you chain async operations instead of nesting callbacks. And for combining multiple promises, the choice of combinator matters: Promise.all rejects as soon as one input rejects, which is right when every result is required, while Promise.allSettled always resolves with the status of every input, which is right when partial success is acceptable."

DDConcept deep dives

Deep dive 1

Resolution and settlement are not identical

A promise is pending, fulfilled, or rejected. Resolving means locking its outcome to a value or another thenable; if it adopts a still-pending promise, it is resolved but not yet settled. This distinction explains why the Promise constructor's resolve function can be called while handlers still wait.

  • Fulfilled and rejected are the two settled outcomes.
  • A promise settles only once; later resolve or reject calls have no effect.
  • Thenable assimilation lets promise implementations and compatible objects compose.

Deep dive 2

Every chain step creates a new promise

then does not mutate the original promise. It returns a new promise whose outcome depends on the selected handler: a returned value fulfills it, a thrown error rejects it, and a returned promise is adopted. A missing handler transparently passes the prior value or rejection onward, which is why one catch can handle failures from earlier steps.

  • Return inner asynchronous work or the chain cannot wait for it.
  • catch is equivalent to then with only a rejection handler.
  • finally observes settlement for cleanup and normally preserves the original outcome.
JavaScript
fetch('/user')  .then((response) => {    if (!response.ok) throw new Error(`HTTP ${response.status}`);    return response.json();  })  .then((user) => renderUser(user))  .catch((error) => renderError(error));

Fetch fulfills even for HTTP error statuses, so the response contract must be checked explicitly. Throwing converts that branch into a rejection handled downstream.

Deep dive 3

Combinators encode failure semantics

Use Promise.all when every result is required, allSettled when each outcome should be inspected, any when the first fulfillment is sufficient, and race when the first settlement determines the aggregate. None of these automatically cancels the operations that lose or become irrelevant, so cancellation remains an explicit resource decision.

  • Result order from Promise.all follows input order, not completion order.
  • Promise.any rejects with AggregateError only if every input rejects.
  • A timeout race should abort the underlying request rather than merely ignore it.

QAInterview questions and model answers

Attempt each answer aloud before opening it. The model answer shows the depth and precision expected in an interview; it is not a script to memorize.

Intermediate · Conceptual · 1 min · Question 1What states can a Promise have?Open model answer

Model answer

A promise is pending, fulfilled with a value, or rejected with a reason. Fulfilled and rejected are settled states. A promise can be resolved to another promise and still remain pending while it adopts that promise's eventual state.

Open question page →
Intermediate · Coding · 1 min · Question 2What does then return?Open model answer

Model answer

then immediately returns a new promise. That promise resolves to the handler's return value, adopts a returned thenable, or rejects if the handler throws. This flattening is what makes promise chains compose without nested callbacks.

JavaScript
fetch('/api/user')  .then((res) => res.json())      // returns a promise -> chain waits for it  .then((user) => user.name)      // plain value -> next promise fulfills with it  .catch((err) => 'anonymous');   // a throw/rejection anywhere lands here
Open question page →
Intermediate · Coding · 1 min · Question 3How do Promise.all and Promise.allSettled differ?Open model answer

Model answer

Promise.all fulfills with ordered values when every input fulfills and rejects as soon as one input rejects. allSettled waits for every input and reports each outcome, making it appropriate when partial failure is expected and every result matters.

JavaScript
await Promise.all([a, b, c]);// -> [va, vb, vc], or rejects on the first rejection
await Promise.allSettled([a, b, c]);// -> [{status:'fulfilled', value}, {status:'rejected', reason}, ...]
Open question page →
Intermediate · Conceptual · 1 min · Question 4Does rejecting Promise.all cancel the remaining operations?Open model answer

Model answer

No. Their results are ignored by that aggregate, but the underlying fetches or other work continue unless they support cancellation and you invoke it. AbortController is commonly used to coordinate fetch cancellation.

Open question page →
Intermediate · Conceptual · 1 min · Question 5What causes an unhandled rejection?Open model answer

Model answer

A rejection has no handler by the time the host performs its reporting check. Returning or awaiting a promise transfers responsibility to the caller; starting a promise and discarding it can create an unhandled rejection unless failure is deliberately handled.

Open question page →
Intermediate · Coding · 1 min · Question 6Are promise handlers synchronous when the promise is already fulfilled?Open model answer

Model answer

No. then, catch, and finally handlers run asynchronously as promise jobs, typically represented as microtasks in browser explanations. This consistent behavior prevents a function from sometimes calling back synchronously and sometimes later.

JavaScript
console.log('start');Promise.resolve().then(() => console.log('then'));console.log('end');
// start  end  then  — the handler always waits for the current task
Open question page →
Advanced · Conceptual · 1 min · Question 7What does Promise.resolve do with a thenable?Open model answer

Model answer

It assimilates the thenable by reading and invoking its then method according to promise resolution rules, protecting against multiple settlement attempts and thrown accessors.

Open question page →
Advanced · Conceptual · 1 min · Question 8How does Promise.race implement a timeout?Open model answer

Model answer

Race can choose a rejecting timer first, but it does not stop the losing operation. A robust timeout also aborts supported underlying work and clears its timer.

Open question page →
Intermediate · Conceptual · 1 min · Question 9What does finally receive?Open model answer

Model answer

A finally callback receives no fulfillment value or rejection reason. It is for outcome-independent cleanup and normally passes the original settlement through.

Open question page →
Beginner · Conceptual · 1 min · Question 10Why avoid the Promise constructor around an existing promise?Open model answer

Model answer

The wrapper adds unnecessary code and often breaks rejection or cancellation handling. Return or chain the existing promise unless adapting a callback API.

Open question page →

SCScenario questions

Scenario 1

Load a user and two independent recommendation feeds. The page should show the user even if one feed fails.

  1. Await the required user request separately.
  2. Start independent feed requests concurrently.
  3. Use allSettled or individual error boundaries for optional data.
  4. Preserve cancellation and report failures without discarding successful results.
Reveal worked answer

I would first obtain the user because later requests may depend on the id. Then I would start both feed requests together and use Promise.allSettled. The UI can render each fulfilled feed and a local fallback for each rejection. I would abort outstanding requests when the route changes and log failures with enough request context.

Verify and go deeper