Explorer
JavaScript

promises in js

JavaScript Theory & Concepts

Promises in JavaScript

Asynchronous lifecycle states, microtask queue scheduling, and chain composition.

📖 The Story & Real-World Analogy

The Busy Espresso Pager Buzzer

"When you order an espresso at a busy coffee roastery, the barista does not make everyone in line wait frozen for 5 minutes while beans grind. Instead, they hand you an electronic pager buzzer. That buzzer is a Promise: • While you wait at your table reading a book, the buzzer is in the **Pending** state. • When your drink is ready, the buzzer flashes green (**Fulfilled**) with your warm coffee. • If the espresso machine explodes, the buzzer flashes red (**Rejected**) with an apology notice. Once the buzzer buzzes, its state is permanently settled—it cannot buzz green and then turn red later!"

A Promise is a proxy for a value that may not be known when the promise is created. It replaced nested callback hell with composable, chainable asynchronous pipelines.

⚙️ How It Works Under The Hood (Step-by-Step)
1

Three Immutable States

`pending` (initial state), `fulfilled` (operation succeeded with a value), `rejected` (operation failed with a reason/error). State changes are one-way and permanent.

2

The Synchronous Executor

The function passed to `new Promise((resolve, reject) => {})` executes synchronously immediately upon instantiation.

3

Microtask Queue Prioritization

Callbacks registered via `.then()`, `.catch()`, or `.finally()` do not run immediately; they are scheduled on the Microtask Queue, which executes before any setTimeout or DOM rendering.

4

Chaining and Flattening

Every `.then()` returns a brand new Promise. Returning a promise inside a `.then()` automatically unwraps it, avoiding nested hierarchies.

💻 Interactive Code Walkthrough

Understanding Promise chaining, error bubbling, and microtask timing:

JAVASCRIPT
console.log("1. Start script");

const fetchUserData = new Promise((resolve, reject) => {
  console.log("2. Executor runs synchronously");
  resolve({ id: 101, username: "dev_alex" });
});

fetchUserData
  .then(user => {
    console.log("4. User received:", user.username);
    return user.id; // Returns a promise resolving to 101
  })
  .then(id => {
    console.log("5. Querying permissions for ID:", id);
  })
  .catch(err => {
    console.error("Caught error in chain:", err);
  })
  .finally(() => {
    console.log("6. Pipeline cleanup finished");
  });

console.log("3. End of synchronous script");
Console Output:
CODE
1. Start script
2. Executor runs synchronously
3. End of synchronous script
4. User received: dev_alex
5. Querying permissions for ID: 101
6. Pipeline cleanup finished
⚠️ Common Pitfalls & Interview Traps
Trap
The Broken Chain (Missing Return)

The Risk: Inside `.then(res => { doWork(res); })`, forgetting to `return` results in the next `.then()` receiving `undefined`.

The Fix: Always return values or nested promises from `.then()` handlers to pass data down the chain.

Trap
The Promise Constructor Antipattern

The Risk: Wrapping an already promise-returning API inside `new Promise((resolve) => fetch().then(resolve))` adds unnecessary complexity and error leaks.

The Fix: Directly return the promise: `return fetch(url).then(res => res.json())`.

⚡ 30-Second Quick Revision Cheat Sheet (TL;DR)
  • ✓ 3 states: `pending` → `fulfilled` OR `rejected` (Settled states are immutable).
  • ✓ Executor runs synchronously; `.then()/.catch()` handlers run asynchronously as Microtasks.
  • ✓ Every `.then()` returns a new Promise.
  • ✓ Unhandled rejections crash Node.js and trigger `unhandledrejection` events in browsers.
  • ✓ Use `Promise.all()`, `Promise.allSettled()`, `Promise.race()`, or `Promise.any()` for concurrent workflows.

Finished this lesson?

Mark this chapter complete to update your learning streak and unlock the next lesson.