DEV Community

Cover image for JavaScript Promises — Office Chai Edition (States, Methods & Event Loop)
Subhrangsu Bera
Subhrangsu Bera

Posted on

JavaScript Promises — Office Chai Edition (States, Methods & Event Loop)

JavaScript Promises are one of the most misunderstood yet essential features in modern JavaScript development.

Most articles explain the syntax.

Very few explain:

  • Why Promises exist
  • How they actually behave internally
  • Why .then() doesn’t run immediately
  • How microtasks change execution order

So let’s break it down properly.

And to make it memorable, we’ll use something every startup developer understands:

The 4:30 PM Office Chai Break

What Is a Promise — Really?

A Promise represents the eventual result of an asynchronous operation. It’s a placeholder for a value that will exist in the future.

That means:

  • A Promise starts in pending
  • It becomes fulfilled (success) or rejected (failure)
  • Once settled, it never changes again

Think of it like ordering chai at work.

You don’t get tea immediately.
You get a commitment that tea will arrive later.

Or maybe it won’t.

That’s a Promise.

☕ The Startup Office Chai Scenario

It’s 4:30 PM.

Sprint is heavy.
Production bug open.
Everyone tired.

Someone says:

“Bhai, chai order karo.”

You place the order.

const chaiOrder = new Promise((resolve, reject) => {
    setTimeout(() => resolve("☕ Chai Arrived!"), 3000);
});

console.log(chaiOrder);
Enter fullscreen mode Exit fullscreen mode

At this moment:

Promise { <pending> }
Enter fullscreen mode Exit fullscreen mode

The chai is pending.

Not here yet.
But expected.

Promise States — Office Meaning

Promise State Office Reality
pending Chai being prepared
fulfilled Chai delivered
rejected Chai wala cancelled
settled Final outcome decided

Important insight:

Even if the Promise resolves immediately,
.then() still won’t execute instantly.

It gets queued.

That’s where microtasks enter the story.

.then()— When Chai Finally Arrives

chaiOrder.then((message) => {
    console.log(message);
});
Enter fullscreen mode Exit fullscreen mode

Meaning:

“Notify me when chai arrives.”

But here’s the deeper truth:

Even if the Promise resolves instantly,
.then() runs after the current call stack clears.

This is because Promise callbacks are queued as microtasks.

They don’t interrupt running code.

They wait politely.

.catch() — Handling Rejection

chaiOrder.catch(() => {
    console.log("No chai today 😭");
});
Enter fullscreen mode Exit fullscreen mode

If chai doesn’t arrive,
you handle the failure gracefully.

Without .catch(), rejected Promises can cause unhandled errors.

In production systems, that’s dangerous.

.finally() — Break Over Either Way

chaiOrder.finally(() => {
    console.log("Back to debugging 🧑‍💻");
});
Enter fullscreen mode Exit fullscreen mode

Whether chai arrived or not,
break time ends.

.finally() always runs after settlement.

Now Let’s Order Multiple Things (Static Methods)

Because one chai is never enough.

Promise.all() — Full Snacks or Nothing

function orderChai() {
    return new Promise((resolve) =>
        setTimeout(() => resolve("☕ Chai ready"), 2000);
    );
}

function orderSamosa() {
    return new Promise((resolve) =>
        setTimeout(() => resolve("🥟 Samosa ready"), 1500),
    );
}

function orderBiscuit() {
    return new Promise((resolve) =>
        setTimeout(() => resolve("🍪 Biscuit ready"), 1000),
    );
}

Promise.all([orderChai(), orderSamosa(), orderBiscuit()])
    .then((items) => {
        console.log("Break Started:", items);
    })
    .catch((error) => {
        console.log("Break Failed:", error);
    });
Enter fullscreen mode Exit fullscreen mode

Output:

Break Started: [
  '☕ Chai ready',
  '🥟 Samosa ready',
  '🍪 Biscuit ready'
]
Enter fullscreen mode Exit fullscreen mode

This returns a single Promise that:

  • Fulfills when all input promises fulfill with array of the fulfillment values
  • Rejects immediately when any one rejects

Office logic:

“Break tabhi jab chai + samosa + biscuit sab aaye.”

If even one fails, the whole break fails.

Use when:

  • All API calls are required
  • All resources must load
  • Deployment depends on everything

Promise.race() — Fastest Wins

function chai() {
    return new Promise((resolve) =>
        setTimeout(() => resolve("☕ Chai arrived"), 2000),
    );
}

function coffee() {
    return new Promise((resolve) =>
        setTimeout(() => resolve("☕ Coffee arrived"), 1000),
    );
}

Promise.race([chai(), coffee()]).then((result) =>
    console.log("First Item:", result),
);
Enter fullscreen mode Exit fullscreen mode

Output:

First Item: ☕ Coffee arrived
Enter fullscreen mode Exit fullscreen mode

Returns a Promise that settles with the first settled input Promise.

It does not care whether that result is success or failure.

Office logic:

Whoever arrives first decides mood.

Used for:

  • Timeout patterns
  • Competing APIs
  • Fastest response logic

Promise.any() — First Success Wins

function failedOrder() {
    return new Promise((_, reject) =>
        setTimeout(() => reject("Out of stock"), 1000),
    );
}

function successfulOrder() {
    return new Promise((resolve) =>
        setTimeout(() => resolve("☕ Chai delivered"), 2000),
    );
}

Promise.any([failedOrder(), successfulOrder()])
    .then((result) => console.log("Success:", result))
    .catch((err) => console.log(err));
Enter fullscreen mode Exit fullscreen mode

Output:

Success: ☕ Chai delivered
Enter fullscreen mode Exit fullscreen mode
  • Fulfills when any promise fulfills
  • Rejects only if all promises reject
  • Returns AggregateError if all fail

Office logic:

“Kuch bhi caffeine mil jaaye.”

Failures ignored unless everyone fails.

Perfect for fallback strategies where at least one success is enough.

Promise.allSettled() — Manager Wants Full Report

const orders = [
    Promise.resolve("☕ Chai ready"),
    Promise.reject("❌ Biscuit unavailable"),
    Promise.resolve("🥟 Samosa ready"),
];

Promise.allSettled(orders).then((results) => {
    console.log(results);
});
Enter fullscreen mode Exit fullscreen mode

Always fulfills with an array of result objects:

[
    { status: "fulfilled", value: "☕ Chai ready" },
    { status: "rejected", reason: "❌ Biscuit unavailable" },
    { status: "fulfilled", value: "🥟 Samosa ready" },
];
Enter fullscreen mode Exit fullscreen mode

Office logic:

The manager wants a full sprint report.

Never rejects.
Always returns structured results.

Ideal for dashboards and logging.

Advanced Promise Utilities — Hidden Power Tools

Now let’s explore less commonly used but powerful Promise utilities.

Promise.resolve() — Instant Chai

const readyChai = Promise.resolve("☕ Instant chai");

readyChai.then(console.log);
Enter fullscreen mode Exit fullscreen mode

Output:

☕ Instant chai
Enter fullscreen mode Exit fullscreen mode

Creates an already fulfilled Promise.

Office analogy:

Chai already on your desk.

But if you pass a thenable:

const thenable = {
    then(resolve) {
        resolve("Followed thenable result");
    },
};

Promise.resolve(thenable).then(console.log);
Enter fullscreen mode Exit fullscreen mode

It will follow that Promise’s state.

Meaning:

  • If it resolves → it resolves
  • If it rejects → it rejects

This is called Promise assimilation.

Promise.reject() — Instant Cancellation

Promise.reject("❌ Order cancelled").catch((error) => console.log(error));
Enter fullscreen mode Exit fullscreen mode

Creates an already rejected Promise.

Useful for:

  • Failing fast
  • Input validation
  • Early exit in async logic

Office analogy:

Chai wala immediately says no.

Promise.try() (Non-standard Utility) — Normalize Sync and Async

Promise.try(() => {
    throw new Error("Something went wrong");
}).catch((err) => console.log(err.message));
Enter fullscreen mode Exit fullscreen mode

Wraps any function:

  • If it returns value → resolved
  • If it throws → rejected
  • If it returns Promise → followed

Office analogy:

You safely handle unpredictable chaiwala behavior.

It unifies sync and async error handling.

Promise.withResolvers() — Manual Control Room

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

promise.then(console.log).catch(console.error);

setTimeout(() => {
    resolve("☕ Chai approved!");
}, 2000);
Enter fullscreen mode Exit fullscreen mode

Returns:

  • A new Promise
  • Its resolve function
  • Its reject function

Separately.

Example:

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

setTimeout(() => resolve("☕ Delivered"), 2000);

promise.then(console.log);
Enter fullscreen mode Exit fullscreen mode

Office analogy:

You hold the “Approve” and “Cancel” buttons yourself.

Useful for:

  • Event systems
  • Deferred patterns
  • Framework internals
  • State machines

How Promise Chaining Actually Flows

Before we dive into the event loop, let’s visualize how Promise chaining creates new Promises and propagates fulfillment or rejection through the chain.

Promise Execution Flow
How Promise fulfillment, rejection, and chaining return a new Promise in the async workflow.

The Real Magic — Microtasks & Execution Order

Consider this:

setTimeout(() => console.log("Timeout"), 0);

Promise.resolve().then(() => console.log("Promise"));

console.log("I am Hero");
Enter fullscreen mode Exit fullscreen mode

Output

I am Hero      ← synchronous
Promise        ← microtask
Timeout        ← task
Enter fullscreen mode Exit fullscreen mode

Even though the timeout delay is 0.

Why?

Because JavaScript always executes in this priority order:

  1. Synchronous code (call stack)
  2. Microtasks (Promise callbacks)
  3. Tasks / macrotasks (setTimeout, setInterval, etc.)

console.log("I am Hero") runs first because it is synchronous — it executes immediately in the call stack before JavaScript even looks at any queues.

Promise.resolve().then() goes to the microtask queue, which has higher priority than the task queue.

setTimeout() goes to the task queue, which runs only after all microtasks are cleared.

So the final order becomes:

I am Hero
Promise
Timeout
Enter fullscreen mode Exit fullscreen mode

Office Analogy

  • Call Stack → Developer’s desk
  • Microtask Queue → High-priority Slack notifications
  • Task Queue → Normal emails
  • Event Loop → Office manager

Work already on the desk is done first.
Slack messages are handled next.
Emails are checked afterward.

That’s why Promises feel “faster” — they’re just processed with higher priority.

How Developers Should Really Think About Promises

A Promise is not just syntax.

It’s a coordination mechanism for asynchronous workflow.

It ensures:

  • Controlled sequencing
  • Error propagation
  • Predictable scheduling
  • Structured async logic

Once you understand that:

JavaScript stops feeling magical.
And starts feeling architectural.

References

Top comments (1)

Collapse
 
leob profile image
leob

Good explanation, nice analogy :-)