Most developers don’t write raw Promises every day — we use tools like useQuery, axios, or helper libraries that wrap everything for us. But when it comes to debugging, reading polyfills, or understanding what those tools are actually doing, everything goes back to Promises.
And especially today, when many beginners rely on AI to generate code, it's super important to truly understand what Promises are doing under the hood.
So let’s start slow, simple, and clear. Step-by-step.— a zero-to-hero journey into JavaScript Promises. चलो शुरू करते हैं.
What is a Promise? (I like this basic and deep definition of a Promise)
A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Introduction
Okay, so you’re here — which means either an interview is coming up soon, or you’re just genuinely curious about how Promises actually work. Both reasons are perfect.
Before Promises, JavaScript handled async work using callbacks (and yes, callbacks are still important in many real-world cases — we’ll cover that in a separate blog). But back then, callbacks often led to messy, nested “callback hell.”
Promises solved that problem, and later async/await made async code feel even smoother.
We’ll go step by step — from basics to advance.
What Exactly Is a Promise?
A Promise is simply a placeholder for a value that you will receive in the future — not right now.
The easiest way to imagine it is like ordering food at a restaurant:
you get a token first, and the actual food (the value) comes later.
A Promise can be in three states:
- pending → the async work is still happening
- fulfilled → the async work finished successfully
- rejected → the async work failed
And once a Promise becomes fulfilled or rejected, its state is locked forever — it cannot change again.
Creating a Promise (executor, resolve, reject)
To create a Promise, you use the new Promise() constructor.
It takes a function (called the executor) with two arguments:
resolve → call this when the async work succeeds
reject → call this when something goes wrong
Here’s the example:
const myPromise = new Promise((resolve, reject) => {
// Simulating async work (like API call)
setTimeout(() => {
const success = true; // change to false to test reject
if (success) {
resolve("Task completed!");
} else {
reject("Something went wrong!");
}
}, 1000);
});
This code creates a Promise that:
waits 1 second
then either resolves with "Task completed!"
or rejects with "Something went wrong!"
But creating a Promise alone does nothing —
you must attach .then() / .catch() to use the result.
Consuming a Promise: then(), catch(), finally()
Once you create a Promise, you need to “consume” it using:
- .then() → runs when the Promise is fulfilled (success)
- .catch() → runs when the Promise is rejected (failure)
- .finally() → always runs, success or failure
Here’s a simple example using the Promise we created earlier:
myPromise
.then((result) => {
console.log("Success:", result);
})
.catch((error) => {
console.log("Error:", error);
})
.finally(() => {
console.log("Promise done!");
});
How this works:
- If the Promise resolves → the code inside .then() runs
- If it rejects → the code inside .catch() runs
- And .finally() runs in both cases (cleanup, loaders, etc.)
Promise Chaining (How values flow)
One of the best features of Promises is chaining — the output of one .then() becomes the input of the next .then().
Think of it like passing a value step-by-step through a pipeline.
Here’s a simple example:
Promise.resolve(1)
.then((num) => {
console.log("Step 1:", num); // 1
return num + 1; // returns 2
})
.then((value) => {
console.log("Step 2:", value); // 2
return value * 10; // returns 20
})
.then((finalValue) => {
console.log("Final Step:", finalValue); // 20
});
✔ How it works:
- The first .then() receives 1
- It returns 1 + 1 = 2
- The second .then() receives 2
- It returns 2 * 10 = 20
- The last .then() receives 20 ** Every .then() creates a new Promise internally, making chaining possible.**
🔥 Important:
If you don’t return anything inside a .then(), the next .then() receives undefined. (Good for interview question)
Error Handling in Promises
Errors in Promises work in a very simple way:
- If any .then() throws an error → the chain jumps to .catch().
- After a .catch(), the chain continues normally (unless you re-throw).
Here’s a clean example:
Promise.resolve("Start")
.then((val) => {
console.log(val); // Start
throw new Error("Something broke!");
})
.then(() => {
// This will NOT run
console.log("This will be skipped");
})
.catch((err) => {
console.log("Caught:", err.message); // Caught: Something broke!
})
.then(() => {
console.log("Chain continues after catch");
});
✔ What’s happening?
- The first .then() runs → prints "Start"
- It throws an error
- The chain skips all other .then() and jumps to .catch()
- After .catch(), the chain continues with the next .then()
🔥 Bonus Tip
If you re-throw inside .catch(), the error will go to the next .catch().
Here you go — a solid list of Promise-based interview coding questions.
These are the ones asked most commonly in frontend + JS rounds, and perfect for devs who preparing for interviews.

Top comments (0)