Charts · 08
The event loop, for interfaces
Every stutter, every spinner that never appears, every click that seems to do nothing for a moment is the same thing: the browser runs one task at a time on the thread that also paints. Start with what that does to an interface, with real work and real frames. Then the rules underneath, stepped through: the microtask queue drains before the next task, rAF runs in the render step, and painting only happens between tasks.
What it does to an interface
1 · The label that never shows
Sets the text to “Saving…” then does 400 ms of work in the same task. You never see “Saving…”: the browser paints after the task ends, by which time it says “Saved”.
2 · The fix: let it paint first
Same work, but after requestAnimationFrame then setTimeout, so a frame paints “Saving…” before the work starts. The loading state exists because the loop was given a turn.
3 · Two animations while you click
Click either button above and watch: the CSS animation keeps moving, because the compositor runs it off the main thread; the JavaScript one stops dead until the task ends. Which is why transform and opacity animations survive a busy page and everything else does not.
The rules underneath, step by step
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');Call stack
- script
Microtask queue · drains completely before the next task
- empty
Task queue · setTimeout, events, I/O
- empty
rAF callbacks · run in the render step
- empty
Console
The script is one task. Microtasks (the promise) drain the moment that task's stack empties, before any other task, so 3 prints before the setTimeout's 2 even at 0 ms.