100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Promises in JavaScript

Understand the Promise object, its three states, and how .then/.catch/.finally chaining replaces nested callbacks.

Asynchronous JavaScriptIntermediate11 min readJul 8, 2026
Analogies

1. Introduction

A Promise is an object representing the eventual completion (or failure) of an asynchronous operation, and its resulting value. Promises were introduced to solve the readability and error-handling problems of nested callbacks. Instead of passing a callback into a function, an async function now returns a Promise, and you attach handlers to it using .then() and .catch(). This lets async steps be chained in a flat, linear structure instead of nesting deeper and deeper.

🏏

Cricket analogy: A Promise is like a bookmaker's slip for a rain-affected match outcome — instead of nesting "if rain stops, then if umpires agree, then if ground is fit" callbacks, you get one slip you attach .then(declareResult) and .catch(abandonMatch) to, chained flatly.

2. Syntax

javascript
// Creating a Promise
const promise = new Promise((resolve, reject) => {
  const success = true;
  if (success) {
    resolve("done");
  } else {
    reject(new Error("failed"));
  }
});

// Consuming a Promise
promise
  .then((value) => console.log(value))
  .catch((error) => console.error(error))
  .finally(() => console.log("cleanup"));

// Combinators
Promise.all([p1, p2, p3]);       // resolves when ALL resolve, rejects if any rejects
Promise.race([p1, p2, p3]);      // settles as soon as ANY settles
Promise.allSettled([p1, p2, p3]); // waits for all, never rejects

3. Explanation

A Promise is always in exactly one of three states: pending (initial, not yet settled), fulfilled (the operation succeeded, with a resulting value), or rejected (the operation failed, with a reason). Once a Promise settles -- becomes fulfilled or rejected -- it is immutable: it cannot change state again, and its value/reason never changes. This immutability is important because it means multiple .then() handlers attached to the same Promise will all see the same final value, no matter when they were attached.

🏏

Cricket analogy: A match result is pending during play, then settles to fulfilled (a winner declared) or rejected (abandoned) — once umpires confirm the result it's immutable, so whether a fan checks the scoreboard immediately or an hour later, they see the same final result.

Each call to .then() returns a NEW Promise, which is what makes chaining possible: .then(a).then(b) runs a, waits for its result (even if a returns another Promise), and passes that result into b. If any handler in the chain throws, or the underlying Promise rejects, control jumps forward to the nearest .catch(). This gives Promises a single, predictable error-handling channel, unlike callbacks where each level needs its own check.

🏏

Cricket analogy: .then(bowlOver).then(reviewDRS) chains because each .then() hands off a fresh result — bowling the over finishes, its outcome feeds into the DRS review — and if a no-ball is missed or DRS fails, control jumps straight to a single .catch(callThirdUmpire) instead of checks at every stage.

Promise callbacks (the functions passed to .then/.catch/.finally) are scheduled as microtasks, not macrotasks. Microtasks run after the currently executing synchronous code finishes, but BEFORE the event loop moves on to the next macrotask (like a setTimeout callback) -- even a setTimeout with a 0ms delay. This is why a resolved Promise's .then() callback consistently fires earlier than a setTimeout(fn, 0) scheduled around the same time.

4. Example

javascript
function getUser(id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Fetched user");
      resolve({ id, name: "Alice" });
    }, 100);
  });
}

function getOrders(user) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Fetched orders for " + user.name);
      resolve(["order1", "order2"]);
    }, 100);
  });
}

console.log("Start");

getUser(1)
  .then((user) => getOrders(user))
  .then((orders) => {
    console.log("Orders:", orders);
  })
  .catch((err) => console.error("Error:", err))
  .finally(() => console.log("Done"));

console.log("End");

5. Output

text
Start
End
Fetched user
Fetched orders for Alice
Orders: [ 'order1', 'order2' ]
Done

6. Key Takeaways

  • A Promise has exactly one state at a time: pending, fulfilled, or rejected -- and is immutable once settled.
  • .then() always returns a new Promise, enabling flat chaining instead of nested callbacks.
  • Errors anywhere in a chain propagate forward to the nearest .catch().
  • Promise callbacks run as microtasks, which always run before the next macrotask (e.g. a setTimeout), even with a 0ms delay.
  • Promise.all fails fast (rejects if any input rejects); Promise.allSettled always resolves with each result's status.
  • An unhandled rejected Promise produces an 'unhandled promise rejection' warning/crash if no .catch() ever handles it.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#JavaScriptProgrammingStudyNotes#Programming#PromisesInJavaScript#Promises#Syntax#Explanation#Example#StudyNotes#SkillVeris