Promises & async/await
Callbacks, promises, async functions, the event loop, and microtasks — what's actually happening.
Why async at all?
JavaScript runs on a single thread. If you call a slow function (a network request, a database query), and JS were synchronous, your entire UI would freeze until it returned. So JS is asynchronous: long-running operations don't block — they run in the background and notify your code when they finish.
The event loop
JS doesn't actually do the work itself for I/O — it asks the runtime (the browser, Node) to handle it, and the runtime calls your code back when ready. The mechanism is called the event loop. There's a call stack and a queue of pending callbacks; whenever the stack is empty, the loop pulls the next callback off the queue and runs it.
This is also why heavy synchronous code freezes your page — it never lets the loop pick up the next task.
Same three fetches. The only difference is whether you `await` them one by one or fire all three first and `await Promise.all`.
Three generations of async code
1. Callbacks (the old way)
setTimeout(() => {
console.log("after 1 second");
}, 1000);
// Nested callbacks → \"callback hell\":
fetchUser(id, (err, user) => {
if (err) return cb(err);
fetchOrders(user.id, (err, orders) => {
if (err) return cb(err);
fetchProducts(orders, (err, products) => {
// ... pyramid of doom
});
});
});2. Promises
A Promise is an object representing a value that may be available now, later, or never. It is in one of three states: pending, fulfilled (with a value), or rejected (with an error).
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(1000)
.then(() => fetch("/api/user"))
.then(r => r.json())
.then(user => console.log(user))
.catch(err => console.error(err));3. async / await — the modern way
`async` and `await` are syntactic sugar over promises. They let you write asynchronous code that READS like synchronous code — top to bottom — but doesn't block.
async function loadUser() {
try {
await delay(1000);
const r = await fetch("/api/user");
const user = await r.json();
console.log(user);
} catch (err) {
console.error(err);
}
}
loadUser();`await` only works inside an `async` function (or at the top level of a module). It pauses execution of THAT function until the promise resolves; the surrounding event loop keeps running. When the promise settles, the function continues from where it paused.
Running things concurrently
If three operations are independent, don't `await` them sequentially — start them all and `await` together with `Promise.all`.
// Sequential — total time = sum
async function slow() {
const a = await fetch("/api/a");
const b = await fetch("/api/b");
const c = await fetch("/api/c");
return [a, b, c];
}
// Concurrent — total time = max
async function fast() {
const [a, b, c] = await Promise.all([
fetch("/api/a"),
fetch("/api/b"),
fetch("/api/c"),
]);
return [a, b, c];
}