JavaScript Promises Explained for Beginners
The Problem: Why Do We Need Promises?
Imagine you order a pizza.
You place the order and the pizza shop says:
"Your pizza will be delivered in 30 minutes."
Now you don't stand outside the shop waiting for 30 minutes. You continue doing other work and receive the pizza when it's ready.
Computers face a similar situation.
Sometimes JavaScript needs to wait for things like:
API requests
Database queries
Reading files
Timers
These tasks take time.
If JavaScript stopped everything and waited, the application would become slow and unresponsive.
So JavaScript starts the task and continues doing other work.
The question is:
How will JavaScript know when the result is ready?
That's where Promises come in.
What is a Promise?
A Promise is an object that represents a value that will be available in the future.
Think of it as:
"I don't have the result right now, but I promise I'll give it to you later."
Example:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Pizza Delivered!");
}, 3000);
});
In this example:
Pizza is not available immediately.
After 3 seconds, it gets delivered.
The Promise notifies us when the result is ready.
Promise States
Every Promise can be in one of three states.
1. Pending
The task is still running.
Example:
const promise = new Promise(() => {});
Current state:
Pending
The result is not ready yet.
2. Fulfilled
The task completed successfully.
resolve("Success");
State:
Fulfilled
Result is available.
3. Rejected
Something went wrong.
reject("Error");
State:
Rejected
An error occurred.
Promise Lifecycle
A Promise always follows this path:
Promise Created
|
v
Pending
/ \
/ \
v v
Fulfilled Rejected
A Promise starts in the Pending state and eventually becomes either Fulfilled or Rejected.
It cannot go back to Pending.
Creating a Promise
Example:
const pizzaPromise = new Promise((resolve, reject) => {
const pizzaReady = true;
if (pizzaReady) {
resolve("Pizza Delivered");
} else {
reject("Pizza Cancelled");
}
});
Here:
resolve()means success.reject()means failure.
Handling Success and Failure
We use .then() and .catch().
pizzaPromise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
Output:
Pizza Delivered
If something goes wrong:
Pizza Cancelled
Understanding .then()
.then() runs when the Promise is fulfilled.
promise.then((result) => {
console.log(result);
});
Think of it as:
"When the task finishes successfully, run this code."
Understanding .catch()
.catch() runs when the Promise is rejected.
promise.catch((error) => {
console.log(error);
});
Think of it as:
"If something fails, run this code."
Promises as Future Values
Let's imagine a friend says:
"I'll send you the notes tomorrow."
Right now you don't have the notes.
But you have a promise that you'll receive them later.
Similarly:
const notesPromise = getNotes();
At this moment:
notesPromise
does not contain the notes.
It contains a Promise that will provide the notes in the future.
Callback Approach
Before Promises, developers often used callbacks.
Example:
getUser(function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
console.log(comments);
});
});
});
Notice how code keeps moving to the right.
This is called:
Callback Hell
or
Pyramid of Doom
Because the code becomes difficult to read and maintain.
Promise Version
The same code can be written using Promises.
getUser()
.then((user) => {
return getPosts(user.id);
})
.then((posts) => {
return getComments(posts[0].id);
})
.then((comments) => {
console.log(comments);
})
.catch((error) => {
console.log(error);
});
This looks much cleaner and easier to understand.
Promise Chaining
A Promise can return another Promise.
This allows multiple asynchronous operations to run in sequence.
Example:
getUser()
.then((user) => {
return getPosts(user.id);
})
.then((posts) => {
return getComments(posts[0].id);
})
.then((comments) => {
console.log(comments);
});
Each .then() waits for the previous Promise to finish.
This is called:
Promise Chaining
Real World Example
Fetching data from an API:
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
Flow:
Send Request
|
v
Pending
|
v
Receive Response
|
v
Fulfilled
If the network fails:
Rejected
Callback vs Promise
| Callback | Promise |
|---|---|
| Can lead to callback hell | Cleaner code |
| Hard to manage errors | Easy error handling |
| Difficult to read | More readable |
| Nested structure | Chaining structure |
Conclusion
Promises help JavaScript handle tasks that take time, such as API calls, database operations, file reading, and timers.
Instead of deeply nested callbacks, Promises provide a cleaner and more readable way to manage asynchronous code.
Think of a Promise as a future value:
"The result is not available right now, but it will be available later."
Once you understand Promises, learning async/await becomes much easier because async/await is built on top of Promises.