Async / await without the headache
JavaScript is single-threaded. One thing at a time. So how does your browser load five images, run a script, and play a video, all at once? Because most of those things are waiting on something else (network, disk, timers), and JavaScript hands the waiting off and moves on.
The system that handles this is called the event loop. Async/await is the syntax that makes it readable.
The Swiggy metaphor
You place an order on Swiggy. The app does not freeze for 30 minutes waiting for biryani. It says "okay, order placed," shows you a tracker, and lets you scroll Instagram. When the food arrives, you get a notification.
That is async. The order is a promise. The notification is the resolve. If the restaurant cancels, that is a reject.
Callbacks were the original mess
Before promises, async work was passed a callback function to run when done.
placeOrder(items, function (err, order) {
if (err) return console.error(err);
trackOrder(order.id, function (err, status) {
if (err) return console.error(err);
notifyUser(status, function (err) { /* and so on */ });
});
});This nesting got a nickname: callback hell. Promises fixed it.
Promises, in one paragraph
A promise is an object that represents a future value. It has three states: pending, fulfilled, rejected. You chain work with .then() and handle errors with .catch().
placeOrder(items)
.then(order => trackOrder(order.id))
.then(status => notifyUser(status))
.catch(err => console.error(err));Flatter, readable, but still a bit ceremonial.
async/await is just nicer promises
async marks a function as asynchronous. await pauses inside that function until a promise settles. The code reads top-to-bottom like normal.
async function order() {
try {
const order = await placeOrder(items);
const status = await trackOrder(order.id);
await notifyUser(status);
} catch (err) {
console.error(err);
}
}try/catch works exactly like synchronous error handling. This is the single best feature added to JS in the last decade.
awaitonly works insideasyncfunctions, or at the top level of an ES module. Random scripts cannot justawait.
Promise.all for parallel work
Sequential await is fine when each step needs the previous one. When they do not, you waste time waiting in line.
const [user, cart, offers] = await Promise.all([
fetchUser(),
fetchCart(),
fetchOffers(),
]);All three fire at once, you wait for the slowest. If any one rejects, the whole Promise.all rejects. Use Promise.allSettled if you want every result regardless.
The event loop, in 60 seconds
JavaScript has a call stack (current work) and a queue (waiting work). When an async task finishes, its callback joins the queue. The event loop picks from the queue only when the stack is empty. That is why a slow synchronous function blocks everything, including UI updates.
In practice: never do heavy CPU work on the main thread. Offload to a worker, or break it up.
Quick recap
- Async exists so the browser does not freeze while waiting on network or timers.
- Promises replaced callbacks. Async/await replaced promise chains.
- Always wrap awaited code in
try/catch. - Parallel work?
Promise.all. Independent results?Promise.allSettled.
Next: fetch, the modern way to actually call an API.
Free tools you can use while you learn
Common questions
Q.What is the difference between async/await and promises?›
Q.Can I use await at the top level of a file?›
Watching quietly. Tap me if you want a tip.
Try this (0 of 1 done)
- 1
Write an async function that logs "step 1", waits 200ms, then logs "step 2". Call it.
show answer
async function go() { console.log('step 1'); await wait(200); console.log('step 2'); } go();