Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Event Loop Tasks, Microtasks, and Paint

Last updated: 5 Oct 20266 min read
tutorial
IntermediateBy AITrove Editorial

JavaScript on a browser main thread runs one task to completion. Promise reactions and queued microtasks run after the current stack clears, before the browser gets the next opportunity to process another task and render. That ordering explains why a state update followed by heavy synchronous work can leave a loading indicator invisible until the work finishes. It also explains why recursively queueing microtasks can starve rendering: each new microtask joins the checkpoint before the browser moves on. Use microtasks for short ordering needs, not as a way to split CPU-heavy work into paint opportunities.

Working case

A reviewer imports 62 inspection rows. The click handler sets a 'Processing' label, then parses every row synchronously and immediately resolves the operation. On a modest device the label appears only after parsing, making the button seem dead. Moving the parser into a promise callback does not fix it if the callback is still a microtask and performs the same long work. The route needs a separate task boundary, a worker, or chunked processing with yields. The label also needs an accessible status path once it actually renders.

Implementation boundary

javascript
const order = [];
setTimeout(() => { order.push("next task"); console.log(order.join(" / ")); }, 0);
Promise.resolve().then(() => order.push("microtask"));
order.push("current task");
// Output: current task / microtask / next task

The trace shows ordering in a simple runtime; a browser may perform rendering between suitable tasks, but this snippet does not prove that a paint occurred. A real UI check should observe when the processing label becomes visible relative to the import work. Keep synchronous handlers short and measure the slowest devices. If a chain of promise callbacks does CPU work without yielding to a future task, its total duration can still block input. Use a task boundary or a worker when the computation is material.

Cost and boundaries

Each task and microtask has scheduling overhead, though the larger cost is the work inside it. Splitting work into tiny units can waste time in scheduling, while one long unit delays input and paint. Measure both total completion time and interaction delay. A short microtask is appropriate for consolidating changes before a checkpoint; a long parser is not. Worker transfer adds another cost that may be worthwhile when it frees the main thread. Do not infer user-visible paint solely from console log order.

Failure trace

A developer replaces the parser loop with Promise.resolve().then(parseAllRows) and marks the loading label before it. The import remains unresponsive because parseAllRows runs as one microtask before the next rendering opportunity. Another attempt recursively queues one microtask per row and still starves the loop. Use a worker or yield between bounded chunks to future tasks, then verify a paint and input response during processing. Test an error halfway through so the label does not remain stuck.

Verification

  • The expected current-task, microtask, next-task order is reproduced.
  • A processing label becomes visible before a long import finishes.
  • A separate control responds during import without corrupting the result.

Practice drill

Create a button that processes 62 rows, sets a visible status, and records time to first paint and time to completion. Compare synchronous parsing, a promise callback, chunked task yields, and a worker. Do not treat the console order as the only evidence. Add a second button and try to activate it during import. The chosen approach should keep the second action responsive while preserving a correct final row count and an error recovery path.

Decision note

Use microtasks for short sequencing, and choose task yields or a worker when work must make room for input and paint.

Common Mistakes

  • Assuming a promise callback moves computation off the main thread.
  • Creating an endless microtask chain to split heavy work.
  • Equating logged order with an observed browser paint.

Connected lessons

Browser Execution and Resource Lifecycle; Workers, Messages, Transfer, and Cancellation; Cooperative Main-Thread Scheduling; Browser Memory and Resource Lifecycles; DOM events: enhance a working control without losing its baseline; Page performance: budget the critical path and reserve layout space; Cooperative Main-Thread Scheduling.

Apply and check

Build Project: responsive import worker and review Web Development: layout and runtime contracts quiz.

Further connections

Interaction Latency Traces and Main-Thread Contention.

web-tech
web-development
Storage details