Back to JavaScript
JavaScript
easy
mid

What are Promises in JavaScript and how do they work?

Objects representing the eventual outcome of an async operation. Three states: pending → fulfilled or rejected, settled once and then immutable. Read via `.then` / `.catch` / `.finally` or await. Static aggregators: `Promise.all`, `allSettled`, `race`, `any`. Replaced callbacks with composable async.

3 min read·~6 min to think through

Cross-link to [[what-is-a-promise-in-javascript-how-do-you-resolve-a-promise-and-what-are-its-us]] which goes deeper. Short version.

States

ts
pending → fulfilled (with value)
rejected  (with reason)

Settles once. Immutable after.

Creating

js
const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve("done"), 100);
});

p.then((v) => console.log(v));

Or:

js
Promise.resolve("immediate");
Promise.reject(new Error("bad"));

Consuming

js
p
  .then((v) => transform(v))
  .catch((e) => handle(e))
  .finally(() => cleanup());

Or with await:

js
try {
  const v = await p;
} catch (e) {
  handle(e);
} finally {
  cleanup();
}

Aggregators

ResolvesRejects
Promise.allAll fulfillFirst rejection
Promise.allSettledAll settleNever
Promise.raceFirst settlesFirst settles with rejection
Promise.anyFirst fulfillsAll rejected → AggregateError

Why they exist

Before promises:

js
getUser(id, (err, u) => {
  if (err) return cb(err);
  getPosts(u.id, (err, p) => { if (err) return cb(err); render(p); });
});

With:

js
const u = await getUser(id);
const p = await getPosts(u.id);
render(p);

Microtasks

.then callbacks run on the microtask queue — before the next macrotask:

js
setTimeout(() => console.log("A"), 0);
Promise.resolve().then(() => console.log("B"));
// B, then A

Anti-patterns

  • Promise constructor anti-pattern — wrapping an existing Promise. Just return it.
  • Forgetting return inside .then — breaks the chain.
  • Unhandled rejections — eventually crash Node.

Interview framing

"A Promise represents an async operation's eventual outcome — pending → fulfilled or rejected, settled once. Create with new Promise((res, rej) => ...) or Promise.resolve / reject. Consume with .then / .catch / .finally or await. Aggregators (Promise.all / allSettled / race / any) let you compose multiple promises. They replaced callback hell with flat, composable async — same single error path via .catch or try/catch. .then callbacks run on the microtask queue, which is why a chained .then always runs before the next setTimeout."

Follow-up questions

  • Show me an example of composing multiple promises.
  • What's the microtask queue?
  • How does async/await relate?

Common mistakes

  • Constructor anti-pattern.
  • No .catch / try/catch.
  • Missing return inside .then.

Performance considerations

  • Microtask floods can starve I/O. Promises themselves are cheap.

Edge cases

  • Resolving with another Promise — chains.
  • Multiple resolve calls — first wins.
  • Synchronous throw in executor → rejection.

Real-world examples

  • fetch, fs.promises, db.query, browser async APIs.

Senior engineer discussion

Seniors choose aggregators deliberately (`all` vs `allSettled`), avoid anti-patterns, and use AbortController for cancellation.

Related questions