Event Loop
Node's event loop is what schedules every async callback — including the microtasks that resolve promises. Knowing the order (sync → microtask queue → macrotask queue) makes async bugs predictable.
Microtask vs macrotask order, await scheduling
EXAMPLE
// 1) The big picture
// Every Node tick runs in this order:
// a) Synchronous code in the current call stack
// b) MICROtasks — Promise callbacks, queueMicrotask
// c) Timers (setTimeout, setInterval) — when their delay is up
// d) I/O callbacks — fs, net, http responses
// e) setImmediate callbacks
// f) Close events (socket close, etc.)
// Microtasks drain between EACH macrotask phase.
console.log('1 sync');
setTimeout(() => console.log('5 timeout'), 0);
setImmediate(() => console.log('6 immediate'));
process.nextTick(() => console.log('3 nextTick')); // even earlier than promises
Promise.resolve().then(() => console.log('4 promise'));
queueMicrotask(() => console.log('4b microtask'));
console.log('2 sync');
// Output:
// 1 sync
// 2 sync
// 3 nextTick
// 4 promise
// 4b microtask
// 5 timeout (or 6 immediate, depending on phase)
// 6 immediate
// 2) await yields the rest of the function to a microtask
async function run() {
console.log('A');
await Promise.resolve(); // suspends here, resumes as a microtask
console.log('C');
}
run();
console.log('B');
// → A, B, C
// 3) Common promise patterns
await Promise.all([fetchUser(id), fetchOrders(id), fetchPosts(id)]);
await Promise.allSettled(urls.map(u => fetch(u)));
async function retry(fn, { attempts = 3, base = 200 } = {}) {
let last;
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch (e) {
last = e;
await new Promise(r => setTimeout(r, base * 2 ** i));
}
}
throw last;
}
// 4) NEVER block the event loop
// Bad — sync CPU work freezes everything else
const hash = crypto.pbkdf2Sync(pw, salt, 600_000, 32, 'sha256');
// Good — async + worker_threads if it's heavy
const hashAsync = await new Promise((res, rej) =>
crypto.pbkdf2(pw, salt, 600_000, 32, 'sha256', (e, k) => e ? rej(e) : res(k)));
Why it matters
Node is single-threaded by design. A 100 ms sync CPU loop holds up EVERY request. Know which APIs are sync (the *Sync ones) and reach for the async / worker_threads version on hot paths.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
console.log('1');
setTimeout(() => console.log('3'), 0);
Promise.resolve().then(() => console.log('2'));
// Output: 1, 2, 3 — microtasks before macrotasks.
Try it Yourself »
Exercise
Promise callbacks are microtasks; setTimeout callbacks are…
tasks
Five letters.
Discussion
Loading…