Let me tell you a story.
Not about code.
About a A Cinematic Guide to JavaScript Asynchronous World, How Phulera Accidentally Explained Asynchronous JavaScript
And trust me — by the end of this story, you’ll understand JavaScript Promises so clearly that even a non-technical person could explain them.
🎬 Episode 1 — The One Computer of Phulera
Phulera Panchayat office has:
- One old desktop computer
- One slow internet connection
- One secretary: Abhishek Tripathi
- And unlimited villagers with problems
Morning 10 AM.
Villagers line up.
Rinki:
“Sachiv ji, certificate chahiye.”
Prahlad ji:
“Pension status check karna hai.”
Vikas:
“Sir, internet nahi chal raha.”
Abhishek sits at the computer.
Important fact: The compuetr can do only one task at a time.If it starts printing then it cannnot upload, If it uploads, it cannot verify Aadhaar.
This computer is JavaScript.
JavaScript is single-threaded. so onely one task runs at a time.That
single running place is called:👉 The call stack.
Now imagine something dangerous.
Abhishek clicks “Verify Aadhaar”.
Government website takes 10 seconds to respond.
If the computer just waits…
Entire office freezes.
No one moves.
No work happens.
Everyone angry.
That would destroy Phulera.
But that’s not what happens.
Because Abhishek learned delegation.
🎬 Episode 2 — Delegation (Asynchronous Behavior)
When the website is slow, abhisekh does something smart.
He submits the requiest, then he moves to next villager.
He does not stare at loading screen.
This is asynchronous behavior.
In javascript:
- long tasks go outside the call stack.
- Browser or Node handles them.
- When finished, they come back.
But now question is:
How does Abhisekh know when work is done?
How does he react?
How does he handle failure?
That coordination system is called:
👉 A promise
🎬 Episode 3 — What Is a Promise?
Block office officer says:
“Abhishek ji, file le li hai. Ya toh kaam ho jayega, ya error aa jayega.”
He did not give result.
He gave commitment.
That is a Promise
A Promise means:
👉 "I will give you result later."
Either:
- it could be Success ( fulfilled )
- it could be Failed ( rejected )
Until then...
It is pending.
🎬 Episode 4 — Three States of Promise (Village Version)
Every promise has 3 states.
1️⃣ Pending — “File Gayi Hai”
Work started.
But no result yet.
Example:
const fileWork = new Promise((resolve, reject) => {
console.log("File sent to Block Office...");
});
console.log(fileWork)
Output:
File sent to Block Office...
Promise { <pending> }
At this moment promise is pending.
2️⃣ Fulfilled — “Kaam Ho Gaya”
const fileWork = new Promise((resolve, reject) => {
resolve("Certificate Approved");
});
fileWork.then(result => console.log(result));
Output:
Certificate Approved
Now the Promise moved:
Pending -> Fulfilled
3️⃣ Rejected — “Server Down Hai”
const fileWork = new Promise((resolve, reject) => {
reject("Server Down");
});
fileWork.catch(error => console.log(error));
Output:
Server down
Pending -> Rejected
🔒 Important Rule — It Cannot Change
Once fullfilled, it can not becaome rejected.
One rejected, it can not become fullfilled.
This is immutability, like government stamp.
Once stamped, it consider as final .
🎬 Episode 5 — Callback Hell (Old Panchayat System)
Before modern system, Abhishek workflow was like this:
First verify Aadhaar.
Then check land.
Then approve scheme.
Then send SMS.
All inside each other.
Let's see in code:
verifyAadhaar(function() {
checkLand(function() {
approveScheme(function() {
sendSMS(function() {
console.log("Work Done");
});
});
});
});
Look at indentation.
Now imagine the error handling:
verifyAadhaar(function(err) {
if (!err) {
checkLand(function(err) {
if (!err) {
approveScheme(function(err) {
if (!err) {
sendSMS(function(err) {
if (!err) {
console.log("Done");
}
});
}
});
}
});
}
});
Problems:
Too much nesting
Hard to read
Manual error checking
Mental headache
This is what we called Callback Hell.
Not just indentation.
It is architechtureal weakness.
Phulera needed structure.
JavaScript got Promises.
🎬 Episode 6 — Promise Handling (.then, .catch, .finally)
now the system becomes clean.
applyForScheme()
.then(result => {
console.log("Approved:", result);
})
.catch(error => {
console.log("Rejected:", error);
})
.finally(() => {
console.log("Register Updated");
});
What happens?
If success:
Approved: PM Awas Yojana
Register Updated
If failure:
Rejected: Document Missing
Register Updated
Notice:
.finally() runs always.
Used for:
- Cleanup
- Closing
- Hiding loadeer
Architecture thinking:
Creation ≠ Reaction
We separate them.
🎬 Episode 7 — Promise Chaining (Sequential Work)
Real Panchayat flow:
- Verify Aadhaar
- Fetch Land
- Approave Scheme
- Send SMS
verifyAadhaar()
.then(aadhaar => fetchLand(aadhaar))
.then(land => approveScheme(land))
.then(result => sendSMS(result))
.then(() => console.log("Process Complete"))
.catch(err => console.log("Error:", err));
Output if success:
Process Complete
If land check fails:
Error: Land Record Not Found
Important:
Error jumps directly to catch.
No need to check at every step.
This is error propagation.
Clean.
🎬 Episode 8 — Promise.all() (Emergency Meeting)
Government demands 3 reports:
- Population
- Road status
- Pension list
All required. JavaScript launches all promises together. No waiting. No sequence. No hierarchy. It expect all three method to be fulfilled.
If even one of promise has failed or reject then whole promises has failed, and it directly goes into the catch block.
Promise.all([ getPopulation(), getRoadStatus(), getPensionList() ])
.then(results => console.log(results))
.catch(err => console.log("Report Failed:", err));
If all succeed:
["5000 people", "Road OK", "120 pensions"]
If one fails:
Report Failed: Road Data Missing
Fail-fast behavior.
All or nothing.
🎬 Episode 9 — Promise.allSettled() (Full Audit)
Now suppose:
Abhishek wants to know what happened,
even if some failed.
function getPopulation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Population Done");
}, 1000);
});
}
function getRoadStatus() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Road Error");
}, 1500);
});
}
function getPensionList() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Pension Done");
}, 500);
});
}
Promise.allSettled([getPopulation(), getRoadStatus(), getPensionList()])
.then(results => {
console.log(results)
});
Ouput:
[
{ status: 'fulfilled', value: 'Population Done' },
{ status: 'rejected', reason: 'Road Error' },
{ status: 'fulfilled', value: 'Pension Done' }
]
Here notice something powerful Promise.allSettled() always resolves. It never rejects.
Instead of failing fast, it gives you structured objects describing what happened:
-
status: "fulfilled"with a value -
status: "rejected"with a reason
Think about that for a second. While Promise.all() will crash the moment anything goes wrong, allSettled() patiently waits for every single promise to finish, no matter what. Good or bad, success or failure, it collects the results and hands them back to you in a neat little package.
And the package is beautifully structured. Every result comes back as one of two shapes:
// When things go right
{ status: "fulfilled", value: "✅ Email sent to user 1" }
// When things go wrong
{ status: "rejected", reason: "❌ Invalid email address" }
No guessing. No try-catch gymnastics. Just clean, predictable data you can loop through and analyze.
This is absolutely perfect when your operations don't depend on each other:
- 📧 Sending 1000 marketing emails — One invalid address shouldn't stop the other 999 from going out
📊 Running parallel analytics jobs — Let each data source report its own success or failure
🖼️ Uploading a batch of photos — A corrupted image shouldn't cancel the whole album upload
With allSettled(), you get the complete picture. You can see exactly what worked, what didn't, and why. Then you make decisions based on real data instead of just watching everything collapse at the first sign of trouble.
Because here's the truth not every mission requires perfection. Sometimes you don't need everything to succeed. Sometimes you just need visibility.
You want to know what happened, learn from the failures, and keep moving forward. That's exactly what Promise.allSettled() gives you.
🎬 Episode 10 — Promise.race() (The "First One Wins" Showdown)
const contractorA = new Promise(resolve =>
setTimeout(() => resolve("👷 Contractor A finished the drywall"), 3000)
);
const contractorB = new Promise(resolve =>
setTimeout(() => resolve("👩 Contractor B finished the drywall"), 1000)
);
Promise.race([contractorA, contractorB])
.then(result => console.log(result));
// Output: 👩 Contractor B finished the drywall
What just happened? We hired two contractors to do the same job. We don't care who does it—we just need it done fast. Contractor B finished in 1 second, Contractor A took 3 seconds. We pay B, send A home, and move on.
The Rule: Promise.race() settles with the result (or error) of the very first promise that finishes. Winners take all. Losers get ignored.
🚨 But What If the Fastest One Fails?
Here's where it gets tricky. Remember—race() only cares about who finishes first. It doesn't care if that finish is a success or a failure.
let's analyze the code:
const speedyContractor = new Promise((_, reject) =>
setTimeout(() => reject("💥 Speedy guy showed up drunk"), 500)
);
const reliableContractor = new Promise(resolve =>
setTimeout(() => resolve("✅ Reliable guy finished perfectly"), 3000)
);
Promise.race([speedyContractor, reliableContractor])
.then(result => console.log("Winner:", result))
.catch(error => console.log("Race abandoned:", error));
// Output: Race abandoned: 💥 Speedy guy showed up drunk
The speedy guy crashed first. Game over. The reliable one finished beautifully—but three seconds too late. Nobody's waiting around to hear that good news.
🏢 Real-World Analogy: The Uber Eats Timer
You're hungry. You order food. The app says "Delivery by 7:30 PM."
But here's the thing—you're not actually waiting forever. Your phone starts a silent race:
const foodDelivery = getFoodFromRestaurant(); // Might take 5 mins, might take 30
const timeout = new Promise((_, reject) =>
setTimeout(() => reject("⏰ Delivery timeout - here's your money back"), 1800000) // 30 min
);
Promise.race([foodDelivery, timeout])
.then(food => console.log("🍔 Food arrived!", food))
.catch(error => console.log("😠", error));
Two promises racing:
- The Food Promise — The driver actually bringing your meal
- The Timeout Promise — A timer that rejects after 30 minutes
If the food wins → 🎉 You eat
If the timer wins → 💸 You get refunded, order cancelled
This is exactly how timeout logic works in real apps. You're not waiting forever for a slow server or a lost delivery driver. You set a deadline, and the first one to cross the finish line decides your fate.
🎬 Episode 11 — Promise.any() (At Least One Helper)
Promise.any() is the optimist of the group. It waits for the first successful promise and ignores any failures along the way. Think of it like calling three friends to help you move—you don't care who cancels, you just need someone to show up with a truck. The moment one friend arrives, you're good to go. If every single friend bails? That's when any() finally gives up and throws an AggregateError saying "everyone failed." It's perfect for fallback scenarios where you have multiple options and just need one to work.
Promise.any([
Promise.reject("Vikas Busy"),
Promise.reject("Prahlad Ji Busy"),
Promise.resolve("Pradhan Ji Arrived")
])
.then(result => console.log(result))
.catch(err => console.log(err));
Step-by-step, here's what JavaScript does:
1. The Setup — You Need Help Moving Furniture
You have a truck, but you can't lift heavy furniture alone. You call three friends:
- Vikas → "Sorry man, I'm busy" (reject)
- Prahlad Ji → "Can't make it today" (reject)
- Pradhan Ji → "On my way!" (resolve)
2. The Race Begins
Promise.any() starts watching all three promises simultaneously:
| Friend | Status | Message |
|---|---|---|
| Vikas | ❌ Rejected immediately | "Vikas Busy" |
| Prahlad Ji | ❌ Rejected immediately | "Prahlad Ji Busy" |
| Pradhan Ji | ✅ Resolved immediately | "Pradhan Ji Arrived" |
3. The "Aha!" Moment
Here's the magic of Promise.any() — it ignores the rejections and keeps waiting for a success. The moment Pradhan Ji's promise resolves, any() grabs that success and runs the .then() block.
.then(result => console.log(result))
// Output: "Pradhan Ji Arrived"
4. What Gets Printed?
You'll see in your console:
Pradhan Ji Arrived
The two rejections? Completely ignored. They're like those friends who cancel—annoying, but not stopping your day.
🔄 What If EVERYONE Failed?
Try this version:
Promise.any([
Promise.reject("Vikas Busy"),
Promise.reject("Prahlad Ji Busy"),
Promise.reject("Pradhan Ji Also Busy") // Now everyone rejects
])
.then(result => console.log(result))
.catch(err => console.log(err));
now you will get something diffrenet in th console:
[AggregateError: All promises were rejected]
AggregateError is JavaScript's way of saying "I tried everyone. Literally everyone said no.😂
🎯 The One-Liner Summary
Promise.any() keeps knocking on doors until one opens. If every door is locked, it comes back with a giant ring of keys saying "none of these worked."
🎬 Episode 12 — async/await (Calm Abhishek)
async function process() {
try {
const aadhaar = await verifyAadhaar();
const land = await fetchLand(aadhaar);
const approval = await approveScheme(land);
console.log("Approved:", approval);
} catch (error) {
console.log("Rejected:", error);
}
}
process();
Looks simple.
Like synchronous code.
But internally uses Promises.
🧘 What's Actually Happening Here?
Abhishek needs to process a government scheme application. It's a three-step nightmare:
- Verify Aadhaar — Takes 10 seconds
- Fetch Land Records — Takes 8 seconds
- Get Final Approval — Takes 5 seconds
In the old days, Abhishek would stand at his desk, doing nothing, waiting for each step to finish. His coworkers would bring him coffee. He'd stare at the screen. Nothing else got done.
Now? Watch the magic:
const aadhaar = await verifyAadhaar();
When Abhishek hits this line, here's what happens:
👉 He puts down his pen and walks away from this task
👉 The office keeps running — phones ring, other files move, coffee gets made
👉 10 seconds later, the Aadhaar result comes back
👉 Abhishek walks back to this exact spot and picks up where he left off
It's like magic. But it's just async/await.
🔍 Peeking Behind the Curtain
Here's the truth: async/await is just fancy syntax for Promises. That whole function? It's secretly doing this:
function process() {
return verifyAadhaar()
.then(aadhaar => fetchLand(aadhaar))
.then(land => approveScheme(land))
.then(approval => console.log("Approved:", approval))
.catch(error => console.log("Rejected:", error));
}
but look at how much cleaner the async/await version is:
With.then()
|
With async/await
|
|---|---|
| Nested chains | Linear like a recipe |
| Callbacks everywhere | Looks like regular code |
| Easy to mess up indentation | Hard to mess up |
| catch() at the end | try/catch you already know |
await pauses only this function.
Office continues working.
🚗 The Car Analogy
Promises with .then() are like driving a manual transmission:
- You're in control
- You understand every gear shift
- But it takes more mental effort
async/await is like driving an automatic:
- Same engine underneath
- Same destination
- You just press gas and go
Both get you there. One just lets you enjoy the ride more.
🧠 The "It Only Pauses THIS Function" Trick
This is the part everyone gets wrong. Look closely:
async function process() {
const aadhaar = await verifyAadhaar(); // ⏸️ Pauses HERE
console.log("Still in process function");
}
console.log("Start");
process();
console.log("End");
What order do these print?
-
"Start"— obviously -
"End"— wait, what? -
"Still in process function"— last!
Because await only pauses the process function. The rest of your JavaScript? Running happily. The office never stops working—only Abhishek's specific task takes a coffee break.
🎯 The Bottom Line
async/await took Promises—which were already pretty good—and made them beautiful.
🎯 The Bottom Line
async/await took Promises—which were already pretty good—and made them beautiful.
| Before | After |
|---|---|
fetchUser().then(user => fetchPosts(user.id)).then(posts => display(posts)) |
const user = await fetchUser(); const posts = await fetchPosts(user.id); display(posts); |
See the difference? Same power. Same performance. Just... calm.
Like Abhishek.
🎬 Episode 13 — Event Loop (The Hidden Panchayat Mechanism)
console.log("Office Open");
setTimeout(() => console.log("Tea Arrived"), 0);
Promise.resolve().then(() => console.log("Block Reply"));
console.log("Villagers Waiting");
Actual Output:
Office Open
Villagers Waiting
Block Reply
Tea Arrived
Wait... what?
If setTimeout is set to 0 milliseconds, shouldn't "Tea Arrived" print immediately? Why does "Block Reply" jump ahead?
Here's what's really happening inside Abhishek's computer:
🪑 The Three Waiting Rooms
Behind the scenes, JavaScript has three rooms where tasks wait their turn:
| Room | What goes There | Example |
|---|---|---|
| call stack | Things happening Right now | console.log |
| Microtask Queue | Promises callbacks (VIP pass) |
.then(), .catch
|
| Microtask Queue | Timers, DOM events |
setTimeout, setInterval
|
⏱️ The 0-Second Myth
When you write setTimeout(..., 0), you're not saying "run this now." You're saying:
"Take this task, put it in the macrotask queue, and run it after everything else is done."
Even with zero milliseconds, it waits.
🧠 The Order of Power
Here's the pecking order JavaScript follows:
1️⃣ Call Stack First — Run everything currently in hand
"Office Open" prints
"Villagers Waiting" prints
2️⃣ Microtask Queue Next — Promise callbacks jump the line
"Block Reply" prints
3️⃣ Macrotask Queue Last — Timers finally get their turn
"Tea Arrived" prints
🏢 The Panchayat Analogy
| JavaScript | Phulera Panchayat |
|---|---|
| Call Stack | Abhishek actively working right now |
| Microtask Queue | Block officer's file (priority) |
| Macrotask Queue | Tea order (can wait) |
Abhishek finishes his current work (call stack). Before making tea, he checks if any block officer files arrived (microtasks). Only after clearing those does he finally make tea (macrotask).
🎯 The One Rule to Remember
Promise jump the queue. Timerss wait their turn.
That's why "Block reply" always beats "Tea Arrived"--even when tea was "ordered" first. Microtask are the VIPs pof javaScript.Time are general admisssion. and the Event Loop is the bouncer making sure everyone enters in the right order.
🌾 Final Scene
Phulera is small.
But system is structured.
Work is delegated.
Responses are coordinated.
Errors are handled.
Flow is controlled.
JavaScript Promises do same thing.
They do not make JavaScript multi-threaded.
They make it organized.
They bring governance to chaos.
Just like Abhishek didn’t control time…
He controlled flow.
And that…
Is what mastering Promises truly means.
Top comments (0)