JavaScript Promises Explained for Beginners
Introduction
In the previous blog, we learned about callbacks and how they help handle asynchronous tasks.
However, callbacks come with a major problem:
Callback Hell — deeply nested and hard-to-read code.
To solve this issue, JavaScript introduced:
Promises
Promises make asynchronous code more readable, structured, and easier to manage.
What Problem Do Promises Solve?
Let’s look at a callback-based example:
setTimeout(() => {
console.log("Step 1");
setTimeout(() => {
console.log("Step 2");
setTimeout(() => {
console.log("Step 3");
}, 1000);
}, 1000);
}, 1000);
Problems:
Nested structure
Hard to read
Difficult to debug
Poor maintainability
This is called callback hell.
What is a Promise?
A Promise is:
An object that represents the future result of an asynchronous operation.
Think of it like this:
- “I promise I will give you the result later.”
Promise States
A Promise has three states:
Pending
Initial state
Task is still running
Fulfilled
Operation completed successfully
Result is available
Rejected
Operation failed
Error is available
Basic Promise Example
Creating a Promise:
const myPromise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Task completed successfully");
} else {
reject("Task failed");
}
});
Handling Success and Failure
We use .then() and .catch():
myPromise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
Explanation:
.then()runs when the promise is fulfilled.catch()runs when the promise is rejected
Real-Life Example
Imagine you ordered a product online:
Order placed → Pending
Delivered → Fulfilled
Cancelled → Rejected
Promise Lifecycle (Concept)
Pending → Fulfilled (resolve)
→ Rejected (reject)
Promise Chaining
One of the biggest advantages of promises is chaining.
Example:
function step1() {
return new Promise((resolve) => {
setTimeout(() => resolve("Step 1 done"), 1000);
});
}
function step2() {
return new Promise((resolve) => {
setTimeout(() => resolve("Step 2 done"), 1000);
});
}
function step3() {
return new Promise((resolve) => {
setTimeout(() => resolve("Step 3 done"), 1000);
});
}
step1()
.then((res) => {
console.log(res);
return step2();
})
.then((res) => {
console.log(res);
return step3();
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
Benefits:
Flat structure (no nesting)
Easy to read
Better error handling
Callback vs Promise
Callback Style:
doTask(function(result) {
doNext(result, function(nextResult) {
doAnother(nextResult, function(finalResult) {
console.log(finalResult);
});
});
});
Promise Style:
doTask()
.then(doNext)
.then(doAnother)
.then(console.log)
.catch(console.error);
Key Difference:
Callbacks → nested
Promises → chained
Common Mistakes
Not returning promises in
.then()Forgetting
.catch()Mixing callbacks and promises unnecessarily
Interview Points
Promises solve callback hell
They represent future values
Three states: pending, fulfilled, rejected
.then()and.catch()are usedSupport chaining
Conclusion
Promises are a powerful improvement over callbacks:
Cleaner code
Better readability
Easier error handling
They are the foundation for modern asynchronous JavaScript.