The Browser Event Loop: Tasks, Microtasks, and Rendering
The browser event loop coordinates tasks, microtask checkpoints, and rendering opportunities. Learn how Promise callbacks differ from tasks and why microtasks can delay a visual update.
Publication & corrections: infostoa · Editorial standards
Individual author and specialist reviewer information is not provided for this article.
A button handler can finish while the screen still appears unchanged, and a Promise.then() callback can run before a setTimeout() callback. The browser is coordinating JavaScript jobs with input, timers, other asynchronous work, and rendering. Its event loop has task queues and a separate microtask queue; treating them as one simple FIFO list leads to incorrect assumptions about ordering and painting.
1. JavaScript execution and the browser event loop
A JavaScript agent processes the currently running job while the browser schedules later work such as user input, timer callbacks, and network-related steps. The HTML Standard distinguishes event loops, one or more task queues, a microtask queue, and rendering steps. This does not mean every browser has one flat queue with a single universal ordering rule. Different task sources can have distinct queues and selection rules.
While synchronous JavaScript is running, the browser waits for that execution to finish before it can make progress on other main-thread work. A long calculation or loop can delay input handling and rendering. An asynchronous API can arrange for a callback to run after the current call stack unwinds, but “asynchronous” does not mean the callback runs on another thread or that the screen updates immediately.
2. How tasks differ from microtasks
Timer and event callbacks commonly run as tasks. Promise reactions and callbacks scheduled with queueMicrotask() are processed as microtasks. Exact task placement depends on the API and its specification, so do not assume all asynchronous callbacks share one ordering rule. A Promise reaction scheduled by the current job is generally processed before a later timer task, but setTimeout(callback, 0) does not promise execution at a precise instant.
The HTML event-loop algorithm performs microtask checkpoints at defined points. During a checkpoint it processes queued microtasks; if one schedules another microtask, that work can be processed in the same drain. A stream that keeps adding microtasks can therefore postpone selecting another task or reaching a rendering opportunity. Repeatedly calling queueMicrotask() to split a large computation does not guarantee that the browser will paint between pieces.
3. Promise completion is not a paint notification
When a Promise settles, its reaction is queued as a microtask. The current synchronous job must finish first, after which the checkpoint processes microtasks. This lets chained Promise work run in relation to the current execution, but it does not promise a paint immediately after a callback. Rendering is a distinct part of the event loop, and a user agent considers rendering opportunities and document state rather than drawing after every task unconditionally.
In the current HTML Standard’s rendering steps, when a rendering opportunity occurs, requestAnimationFrame() callbacks run before style and layout are updated and the frame is painted. A browser can skip a rendering update, so the callback is not a synchronous refresh or an instruction to paint immediately. This is a simplified description of the specified step order, not a claim that every task is followed by a frame.
For a visual change that should align with a display frame, requestAnimationFrame() may fit better. If substantial computation must yield so the page can handle input, split the work across tasks or consider a worker where appropriate. Pick the mechanism according to the requirement: Promises compose asynchronous results, but they are not a frame-yielding scheduler.
4. Practitioner perspective: inspect ordering and responsiveness separately
For a timing bug, record when synchronous code ends and which task or microtask callbacks were scheduled. A browser performance trace can show long main-thread work alongside rendering activity, helping distinguish “the callback ran” from “the user saw the update.” Do not treat a zero-delay timer as a clock or assume a microtask callback runs only once.
Wrapping a long synchronous calculation in a Promise does not by itself improve responsiveness. Reduce or divide the work, and use frame-oriented APIs when a visual update depends on a display frame. Task-source details and scheduling behavior are defined by browser standards and implementations, so measure important behavior in the browsers the application supports.