JavaScript Promises Explained for Beginners
JavaScript is single-threaded, but it often needs to handle tasks that take time—like fetching data from an API, reading a file, or waiting for a timer.
If JavaScript waited for each task to finish before moving on, the whole app would freeze.
That’s where Promises come in.
A Promise is a JavaScript object that represents a value that will be available now, later, or maybe never.
In simple words:
A Promise is a future result of an asynchronous operation.
Why Were Promises Introduced?
Before promises, developers mostly used callbacks for asynchronous code.
Callback Example :-
setTimeout(() => {
console.log("Data loaded");
}, 2000);
This works, but when many async operations depend on each other, callbacks can become messy.
Callback Hell Example:-
getUser(function(user) {
getOrders(user.id, function(orders) {
getPayment(orders[0], function(payment) {
console.log(payment);
});
});
});
This deeply nested structure becomes hard to:
read
debug
maintain
Promises were introduced to make asynchronous code:
more readable
easier to manage
better for chaining multiple async tasks
What Problem Do Promises Solve?
Promises solve the problem of managing asynchronous operations cleanly.
They help with:
Avoiding callback hell
Handling success and errors in a structured way
Chaining async tasks in a readable manner
Writing more predictable async code
Stages of a promise:-
A Promise has 3 states:
Pending
Initial state
Operation is still running
Fulfilled
Operation completed successfully
Promise returns a value
Rejected
Operation failed
Promise returns an error/reason
Important:
A promise can only settle once.
That means:
pending → fulfilled
orpending → rejected
Once settled, it cannot change again.
Example:-
const myPromise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Task completed successfully");
} else {
reject("Task failed");
}
});
Explanation:
new Promise(...)creates a promiseIt takes a function called the executor
The executor receives:
resolve→ call when task succeedsreject→ call when task fails
Handling Promise Results
Promises are usually handled with:
.then()→ for success.catch()→ for failure.finally()→ runs no matter what
Example:-
myPromise
.then((result) => {
console.log("Success:", result);
})
.catch((error) => {
console.log("Error:", error);
})
.finally(() => {
console.log("Promise finished");
});
Promise Lifecycle Example:-
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received from server");
}, 2000);
});
console.log("Start");
fetchData
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
console.log("End");
Output:-
Start
End
Data received from server
Why?
Because:
Promise starts async work
JavaScript does not block
It continues running the next lines
When the async task completes,
.then()runs later
This is what makes JavaScript non-blocking.
Promise Chaining
One of the biggest benefits of promises is chaining.
Instead of nesting callbacks, you can return values from .then() and continue the flow.
Example:-
const orderFood = new Promise((resolve) => {
setTimeout(() => {
resolve("Pizza");
}, 1000);
});
orderFood
.then((food) => {
console.log("Ordered:", food);
return food + " with extra cheese";
})
.then((updatedOrder) => {
console.log("Updated Order:", updatedOrder);
return "Bill generated";
})
.then((billStatus) => {
console.log(billStatus);
})
.catch((error) => {
console.log("Something went wrong:", error);
});
Why chaining is useful:
cleaner flow
avoids deep nesting
easier to debug
easier to maintain
Returning a Promise Inside .then()
Sometimes the next step is also asynchronous.
Example:-
function step1() {
return new Promise((resolve) => {
setTimeout(() => resolve("Step 1 done"), 1000);
});
}
function step2(message) {
return new Promise((resolve) => {
setTimeout(() => resolve(message + " → Step 2 done"), 1000);
});
}
step1()
.then((result) => {
console.log(result);
return step2(result);
})
.then((finalResult) => {
console.log(finalResult);
})
.catch((error) => {
console.log(error);
});
If a .then() returns a promise, the next .then() waits for it to finish.
This is a very important promise concept.
Handling Errors in Promises
Errors can happen in two ways:
reject() is called
- code inside
.then()throws an error
Example:-
const riskyTask = new Promise((resolve, reject) => {
let success = false;
if (success) {
resolve("All good");
} else {
reject("Something failed");
}
});
riskyTask
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log("Caught error:", error);
});
Errors in Chaining:-
Promise.resolve(10)
.then((num) => {
return num * 2;
})
.then((num) => {
throw new Error("Unexpected issue");
})
.then((num) => {
console.log(num);
})
.catch((error) => {
console.log("Error handled:", error.message);
});
If an error happens anywhere in the chain, .catch() can handle it.
Real-World Example: API Request
Promises are commonly used when calling APIs.
fetch("https://jsonplaceholder.typicode.com/users/1")
.then((response) => response.json())
.then((user) => {
console.log("User name:", user.name);
})
.catch((error) => {
console.log("API Error:", error);
});
Here:
fetch()returns a promiseresponse.json()also returns a promiseThat’s why chaining is needed


