Javascript Promises Explained for beginners

Hello readers,
Welcome back to another blog. In the last blog, we talked about callbacks - why they exist and the problems they bring (callback hell, pyramid of doom, dependency chaos).
If you haven't read that yet, read here:
https://js-with-abhishek.hashnode.dev/callbacks
Now think about this
What if we could handle async code in a cleaner way?
What if we could avoid deeply nested callbacks?
What if our code looked more like normal step-by-step logic?
That’s where Promises come in.
What Problem Do Promises Solve?
Let’s recall the classic callback problem:
orderFood(function(order) {
makePayment(order, function(payment) {
deliverFood(payment, function(delivery) {
console.log("Delivered:", delivery);
});
});
});
This creates:
Nested structure (callback hell)
Hard to read
Hard to debug
Error handling becomes messy
Promises solve this by giving us:
Better readability
Linear flow of async code
Centralized error handling
What is a Promise?
A Promise is basically a future value.
It represents:
“I don’t have the result right now… but I will give it to you later.”
Example:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Food delivered ");
}, 2000);
});
Here:
resolve→ successreject→ failure
We will understand this code snippet ahead.
Promise States
Every promise has 3 states:
Pending
Initial state.
Work is still going on
Fulfilled
Operation completed successfully,
resolve() is called
Rejected
Operation failed
reject() is called
Think of it like ordering food:
Pending → Order placed
Fulfilled → Food delivered
Rejected → Order canceled
Basic Promise Lifecycle
const orderFood = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Order successful");
} else {
reject("Order failed");
}
});
Now consuming it:
const orderFood = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Order successful");
} else {
reject("Order failed");
}
});
Flow:
Promise created
Async work happens
Either:
resolve()→.then()runsreject()→.catch()runs
Handling Success and Failure
Success → .then()
promise.then((data) => {
console.log("Success:", data);
});
Failure → .catch()
promise.catch((error) => {
console.log("Error:", error);
});
Always Run → .finally()
promise.finally(() => {
console.log("Done (success or failure)");
});
Promise Chaining (Most Important)
This is where promises shine.
Instead of nesting, we chain:
orderFood()
.then((order) => makePayment(order))
.then((payment) => deliverFood(payment))
.then((delivery) => {
console.log("Delivered:", delivery);
})
.catch((error) => {
console.log("Something went wrong:", error);
});
What changed?
No nesting
Clean flow
Looks synchronous (but isn’t!)
So yeah… that’s Promises in a nutshell
We started with messy callbacks…
Moved to cleaner, structured async handling…
And saw how promises make our code readable, predictable, and scalable.
But wait…
Promises themselves come with some powerful built-in methods that can take things to the next level — especially when dealing with multiple async operations.
Things like:
Running multiple promises together
Handling the fastest or all results
Managing failures smartly
I’ve already covered all of that in detail in my dedicated blog on Promise Methods
🔗 Read it here:
https://js-with-abhishek.hashnode.dev/
And in the next blog… We’ll take one more step forward:
async/await — making async code look like sync magic
Stay consistent and keep grinding.
Peace ✌️





