Node.js · 01/19 6 min
The event loop, visually
Call stack, microtasks, macrotasks: why Promise callbacks beat setTimeout(0).
1console.log("A");
2setTimeout(() => console.log("B"), 0);
3Promise.resolve().then(() => console.log("C"));
4console.log("D");
console
call stack
main()
microtasks (promises)
macrotasks (timers)
1/8The script itself is the first task. It runs top to bottom on the call stack.
JavaScript runs one thing at a time on the call stack. Everything asynchronous is a callback that waits in a queue until the stack is empty.
The rules
- Run the current script to completion (synchronous code first, always).
- Drain all microtasks:
Promise.then,awaitcontinuations,queueMicrotask. - Take one macrotask: a
setTimeout, I/O callback orsetImmediate. - Drain microtasks again. Repeat.
So the output of the classic puzzle above is A, D, C, B: synchronous logs first, then the promise callback, then the timer, even with a 0 ms delay.
Why debugging rounds care
- Forgotten
await: the function returns a pending Promise.if (promise)is always truthy, so a failed password check "passes". array.forEach(async x => …)doesn't wait: forEach ignores the returned promises. Usefor…ofwithawait, orawait Promise.all(array.map(…)).- Sequential awaits in a loop are slow when the calls are independent;
Promise.allruns them concurrently. - Unhandled rejections: an
asyncExpress handler that throws without try/catch never reaches your error middleware in Express 4.
// ❌ returns before any save finishes
items.forEach(async (it) => { await it.save(); });
// ✅
await Promise.all(items.map((it) => it.save()));
Practise it now
Next in Node.js
Express request pipeline