The Restaurant Ticket Analogy
You're at a busy restaurant. You order food at the counter.
What happens:
- You order (make a request)
- You get a ticket with a number (not the food itself!)
- You sit down and wait
- When ready, they call your number
The ticket is a promise. It doesn't give you food. It's like an agreement that:
- You WILL get your food eventually (fulfilled), OR
- They'll tell you if something went wrong (rejected)
JavaScript Promises work the same way.
A Promise is a placeholder for a future value. Instead of freezing while waiting for slow operations, you get a Promise - an object that will eventually contain your result (or an error).
Why Promises Exist
The Problem: Callback Hell
Before Promises, handling async operations meant nested callbacks:
getUser(function(user) {
getOrders(user, function(orders) {
getDetails(orders, function(details) {
// 3 levels deep already!
});
});
});
This "pyramid of doom" was hard to read, write, and maintain.
Promises flatten the code:
getUser()
.then(user => getOrders(user))
.then(orders => getDetails(orders))
.then(details => doSomething(details))
.catch(error => handleError(error));
Same logic, but readable!
The Three States
A Promise is in one of three states at a time:
| State | Meaning | What Happens |
|---|---|---|
| Pending | Waiting | Operation in progress |
| Fulfilled | Success | .then() handler runs |
| Rejected | Failed | .catch() handler runs |
Promise created → PENDING
↓
(waiting)
↓
Result ready?
↙ ↘
SUCCESS ERROR
↓ ↓
FULFILLED REJECTED
Once a Promise settles (fulfilled or rejected), it cannot change - it's locked in that state forever.
How to Use Promises
Step 1: Get a Promise
const promise = fetch("/api/users"); // Returns a Promise
Step 2: Handle success with .then()
promise.then((result) => {
console.log("Got it!", result);
});
Step 3: Handle errors with .catch()
promise
.then((result) => console.log(result))
.catch((error) => console.log("Failed:", error));
Chaining: One After Another
Promises can chain - each .then() returns a new Promise:
fetch('/api/user')
.then(response => response.json()) // Returns Promise
.then(user => fetch('/api/orders/' + user.id)) // Returns Promise
.then(response => response.json()) // Returns Promise
.then(orders => console.log(orders)) // Final result
.catch(error => console.log(error)); // Catches ANY error above
The chain flows top to bottom. If any step fails, it jumps straight to .catch().
Creating Your Own Promises
Sometimes you need to wrap non-Promise operations:
const myPromise = new Promise((resolve, reject) => {
// Do something async...
if (success) {
resolve(result); // Fulfill with result
} else {
reject(error); // Reject with error
}
});
Example: Wrapping setTimeout
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
wait(delayMs).then(() => console.log("Done waiting!"));
Running Multiple Promises
Promise.all(): Wait for ALL to finish
Promise.all([fetchUsers(), fetchProducts(), fetchOrders()])
.then(([users, products, orders]) => {
// All three finished!
});
If any fails, the entire thing fails.
Promise.race(): First one wins
Promise.race([fetchFromServer1(), fetchFromServer2()])
.then(result => {
// First one to finish, use that result
});
Promise.allSettled(): Get all results (even failures)
Promise.allSettled([mayFail1(), mayFail2(), mayFail3()])
.then(results => {
// Each result has { status, value } or { status, reason }
});
Real-World Examples
1. Fetching Data
1. User clicks "Load Data"
2. fetch() returns a Promise
3. Page shows loading spinner
4. Promise fulfills → Show data
OR Promise rejects → Show error message
2. Form Submission
1. User submits form
2. Send to server (returns Promise)
3. Show "Submitting..."
4. Promise fulfills → "Success!"
OR Promise rejects → "Please try again"
3. Image Loading
1. Start loading image
2. Promise pending...
3. Image loads → Display it
OR Image fails → Show placeholder
Common Mistakes
Mistake 1: Forgetting to Return in Chains
// ❌ Buggy chain - forgot return!
fetch("/api/user")
.then((response) => {
response.json(); // No return!
})
.then((user) => {
console.log(user); // undefined!
});
// ✓ Correct
fetch("/api/user")
.then((response) => response.json()) // Returns the Promise
.then((user) => console.log(user)); // Gets the result
Mistake 2: Not Catching Errors
// ❌ Unhandled rejection!
fetch("/api/data").then((response) => response.json());
// What if it fails?
// ✓ Handle errors
fetch("/api/data")
.then((response) => response.json())
.catch((error) => console.log("Failed:", error));
Mistake 3: Nested .then() Calls
// ❌ Back to callback hell!
fetch("/user").then((user) => {
fetch("/orders/" + user.id).then((orders) => {
// Nested again!
});
});
// ✓ Chain properly
fetch("/user")
.then((user) => fetch("/orders/" + user.id))
.then((orders) => console.log(orders));
Promises vs Async/Await
Async/await is just cleaner syntax for Promises:
| Promises | Async/Await |
|---|---|
.then() chains | await statements |
.catch() for errors | try/catch blocks |
| Explicit Promise handling | Reads like sync code |
Same thing, different syntax:
// Promises
getUser()
.then((user) => getOrders(user))
.then((orders) => console.log(orders));
// Async/Await (equivalent)
const user = await getUser();
const orders = await getOrders(user);
console.log(orders);
FAQ
Q: What's the difference between Promise and callback?
Promises are objects representing future values. Callbacks are functions you pass to be called later. Promises provide cleaner chaining and error handling.
Q: Can a Promise be fulfilled AND rejected?
No. Once a Promise settles (either state), it's locked. It can't change.
Q: What happens if I don't handle a rejected Promise?
Many runtimes show an "unhandled rejection" warning. It's a good idea to handle rejections (with .catch() or try/catch around await).
Q: When should I use Promise.all()?
When you need multiple independent operations to ALL complete before proceeding.
Q: Can I cancel a Promise?
Standard Promises can't be cancelled. AbortController can be used with fetch() for cancellation.
Q: Should I use Promises or async/await?
Async/await is usually cleaner for most cases. Use raw Promises for advanced patterns like Promise.all().
Summary
Promises are objects that represent eventual completion (or failure) of an async operation. They flatten callback hell into readable chains.
Key Takeaways:
- Promise = placeholder for a future value
- Three states: pending, fulfilled, rejected
.then()for success,.catch()for errors- Chain promises for sequential operations
- Promise.all() for parallel operations
- Handle rejections.
- Async/await is cleaner syntax for the same thing
Promises are fundamental to modern JavaScript. Master them and async code becomes straightforward!
Related Concepts
Leave a Comment
Comments (0)
Be the first to comment on this concept.
Comments are approved automatically.