Event loop
| One thread | |
| Micro vs macro | |
| Do not block | |
| Measure with console.time |
One thread
JavaScript runs user code on one thread with an event loop around it. Async tasks wait in the background while the thread handles ready callbacks one by one. This model stays simple as long as no single callback hogs the thread.
console.log("first");
setTimeout(() => {
console.log("timer");
}, 0);
console.log("second");
Micro vs macro
Promise callbacks are micro tasks and timer callbacks are macro tasks. After each main turn the loop drains all ready micro tasks before the next timer. That is why a resolved promise prints before a zero delay timer.
setTimeout(() => {
console.log("macro");
}, 0);
Promise.resolve("value").then((v) => {
console.log(v);
});
Do not block
A long tight loop blocks timers, network events, and promise progress. Split heavy work into chunks and yield with a short timer or an awaited delay between chunks. The loop then stays responsive while total speed stays close to the same.
async function chunks(): Promise<void> {
for (let i = 0; i < 3; i++) {
console.log(i);
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
Measure with console.time
Use console dot time around an await to learn the real wait. Start the timer, await the work, then stop the timer with the same label. Compare serial and parallel shapes on real data before tuning.
async function timed(): Promise<void> {
console.time("work");
await new Promise((resolve) => setTimeout(resolve, 50));
console.timeEnd("work");
}
timed();
Next: assert tests
Article author: Arthur Isaev