JavaScript Promises Without the Headache
You order food at a busy counter. The cashier doesn't make you stand there holding up the line while the kitchen cooks. You get a token with a number on it. You sit down. One of two things happens later: your number is called and you collect your food, or someone comes out and tells you the kitchen has run out of what you ordered.
That token is a promise. Everything else in this article is just the token system with code attached.
The token, in code
A promise is an object that stands in for a result that doesn't exist yet. It starts pending (you're holding the token, waiting). It ends in exactly one of two states:
- Fulfilled — your number is called. You collect the food. This is
.then(). - Rejected — the kitchen failed. Someone tells you why. This is
.catch().
const order = getFoodToken(); // returns a Promise
order
.then(meal => eat(meal)) // token called: collect your food
.catch(problem => orderSomethingElse(problem)); // kitchen ran out: do this instead
fetch() hands you a token. The menu hasn't downloaded yet, but you already have the object, and you can attach instructions for both endings right now:
fetch("https://example.com/menu")
.then(response => response.json())
.then(menu => showMenu(menu))
.catch(err => showError("Menu failed to load"));
Read it as a sentence: fetch the menu, then read it, then show it. If anything in that chain fails, the catch at the bottom handles it. One catch covers the whole chain, the same way one announcement covers everyone holding a token from that kitchen.
async/await is the same kitchen, better seating
async and await don't replace promises. They let you write the token flow as if you had a waiter:
async function loadMenu() {
try {
const response = await fetch("https://example.com/menu");
const menu = await response.json();
showMenu(menu);
} catch (err) {
showError("Menu failed to load");
}
}
await means: pause here until this token is called, then hand me the food. The try/catch is your .then() and .catch() wearing different clothes. Underneath, it is the same promise. Use whichever reads better in the code you're writing; most people find await easier once a function has three or four steps.
The 3 mistakes that cause most promise bugs
1. Forgetting to return inside .then(). This one bites everyone.
// broken: the next .then receives undefined
fetch(url)
.then(response => { response.json(); })
.then(data => console.log(data)); // undefined
// fixed: return the next token
fetch(url)
.then(response => response.json())
.then(data => console.log(data));
Why: each .then() passes its return value to the next one. response.json() itself returns a promise, and if you don't return it, the chain doesn't wait for it. It's like collecting your token number but never handing it to the person at the next counter. The food exists; nobody brings it to you.
2. Mixing callbacks into a promise chain. You'll meet older functions that want a callback instead of returning a promise. Don't thread them through a chain. Wrap them once, at the edge:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
await wait(1000); // a clean token, no callback nesting
One wrapper, written once, and the rest of your code stays flat and readable.
3. Skipping .catch(). A rejected promise with no catch doesn't stop your program politely. It produces an unhandled rejection, which usually means the user stares at a spinner forever and you get a confusing console warning hours later. In token terms: the kitchen called out that your order failed, and nobody at your table was listening. Every chain gets a catch, or every async function gets a try/catch. No exceptions, even for code you're "sure" won't fail. Kitchens run out of things.
The 10-line cheat summary
- A promise is a token for a result that isn't ready yet.
- Pending means you're still holding the token.
- Fulfilled means the food arrived:
.then()runs. - Rejected means the kitchen failed:
.catch()runs. fetch()returns a promise, not data.- Return values inside
.then(), or the next step getsundefined. - Returning a promise inside
.then()makes the chain wait for it. awaitpauses an async function until the token is called.try/catcharound awaits is.then()/.catch()in a calmer format.- No catch means silent failure. Always attach one.
Keep the token picture in your head for a week and promises stop being a topic you revise and start being a thing you just read. The syntax was never the hard part. The waiting was.
Comments
Join the discussionNo comments yet. Be the first to comment.
