Lesson 4 of 5
The event loop, promises and async/await
2 min read
JavaScript runs your code on a single thread. It handles slow operations (network, timers, files) without blocking by handing them to the browser or Node, then running a callback when they finish.
The event loop in four parts
- Call stack: the functions currently running.
- Web APIs / Node APIs: timers, fetch, file I/O run here, outside your thread.
- Task queue (macrotasks): callbacks from
setTimeout,setInterval, I/O, UI events. - Microtask queue: promise callbacks (
.then,awaitcontinuations),queueMicrotask.
The loop: run the current script to completion → run all microtasks → render if needed → take one macrotask → run all microtasks → repeat.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 1 4 3 2Synchronous code first (1, 4), then microtasks (3), then the timer (2), even with a 0 ms delay.
Promises
A promise is a placeholder for a value that arrives later. States: pending → fulfilled or rejected (settled once, never changes again).
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
fetch("/api/jobs")
.then((res) => res.json())
.then((jobs) => render(jobs))
.catch((err) => showError(err)) // catches any error above
.finally(() => hideSpinner());Combinators
| Method | Resolves when | Rejects when |
|---|---|---|
Promise.all | all fulfil (array of results) | any one rejects (fail fast) |
Promise.allSettled | all settle (array of {status, value/reason}) | never |
Promise.race | first one settles (either way) | first one rejects |
Promise.any | first one fulfils | all reject |
async / await
async functions always return a promise. await pauses the function (not the thread) until the promise settles.
async function loadDashboard(userId) {
try {
// Run independent requests in parallel, not one after another
const [user, jobs] = await Promise.all([getUser(userId), getJobs(userId)]);
return { user, jobs };
} catch (err) {
console.error(err);
throw new Error("Could not load dashboard");
}
}Common mistakes:
awaitinside aforloop when the calls are independent (slow, sequential). UsePromise.all.arr.forEach(async (x) => await save(x))does not wait. Usefor...ofwithawait, orPromise.all(arr.map(save)).- Forgetting
try/catch(or.catch) leads to unhandled rejections.
Implement Promise.all (a frequent ask)
function promiseAll(items) {
return new Promise((resolve, reject) => {
const results = [];
let done = 0;
if (items.length === 0) return resolve(results);
items.forEach((item, i) => {
Promise.resolve(item).then((value) => {
results[i] = value; // keep the original order
if (++done === items.length) resolve(results);
}, reject);
});
});
}Check yourself
- Predict the output of a mix of
setTimeout,Promise.thenandconsole.log. - Why doesn't
awaitinsideforEachwork as expected? - Difference between
Promise.allandPromise.allSettled?
Sign up free to save your progress.