Browser vs Node — Where the Event Loop Actually Diverges (Part 2/3)

2026年8月4日1 次浏览来源:Dev.to阅读原文

In part 1, we built the shared mental model: call stack, microtask queue, macrotask queue, and the rule that microtasks fully drain before the next macrotask runs.

That model is spec-level JavaScript behavior — but it's not the whole story once you actually run code.

The event loop isn't part of the JS language spec.

It's part of the host environment — the browser or Node — and each one implements it differently around that shared core.

This is the post most "event loop" explainers skip, because it means going past the diagram and into how each runtime is actually built.

The browser: event loop meets rendering In a browser, the event loop isn't just juggling callbacks — it's also responsible for keeping the page visually responsive.

That means rendering has to get a turn too, and the browser has to decide when.

Here's the roughly accurate sequence per loop iteration: Execute one macrotask (a click handler, a callback, a network event, whatever's next in the queue) Drain the entire microtask queue Maybe render a frame — the browser doesn't render after every single task; it tries to hit ~60fps and will batch work between paints Go back to step 1 The "maybe render" part is where two APIs come in that don't exist in Node at all: — schedules a callback to run right before the next repaint.

It's not a macrotask or microtask in the queue sense — it's tied directly to the rendering pipeline.

Use it for anything visual (animations, DOM measurements) instead of , because it's synced to when the browser is actually about to paint, not an arbitrary delay. — schedules a callback to run when the browser is idle, after layout and paint, with a deadline.

Meant for low-priority work you don't want competing with rendering — analytics, prefetching, non-urgent DOM updates.

Here's the key interaction that's easy to miss: microtasks can starve rendering.

If a promise chain keeps queueing more microtasks, the browser can't get to the paint step, because microtasks always drain fully before rendering gets a turn.

This is a real, debuggable performance bug — a runaway chain can visibly freeze a page even though "nothing is blocking the main thread" in the traditional sync-loop sense.

Compare that to a -based recursive loop — because each iteration is a separate macrotask, the browser gets a chance to render between them.

Node: no rendering, but a much more structured loop Node doesn't render anything, so it doesn't need the "maybe paint" logic.

Instead, it's built on libuv, a C library that gives Node its event loop, thread pool, and async I/O. libuv organizes the loop into distinct phases, each with its own FIFO queue of callbacks.

This is a meaningfully different shape from the browser's single task queue.

The phases, in order, each loop tick: timers — runs callbacks scheduled by / whose threshold has elapsed pending callbacks — executes I/O callbacks deferred from the previous loop iteration (some system-level TCP errors, etc.) idle, prepare — internal use only poll — the big one: retrieves new I/O events, executes I/O-related callbacks (almost everything — file reads, network requests).

Node will block here waiting for new events if there's nothing else scheduled check — callbacks run here, specifically after poll close callbacks — e.g.

Then it loops back to timers.

Between every single callback — not just between phases, but between individual callbacks within a phase — Node drains the microtask queue.

Same drain-fully rule as the browser, just applied at a finer grain because there's no rendering to interleave with. — Node's queue-jumper Node has a queue that doesn't exist in the browser at all: .

Despite the name, it doesn't queue for "next tick" of the event loop — it runs before microtasks, after the current operation finishes, no matter what.

Priority order in Node, after any synchronous code completes: queue (fully drained) Promise microtask queue (fully drained) Next macrotask/phase callback Output: , , , beats the promise every time, be

分享
Baike.dev

baike.dev helps you discover great languages, frameworks, databases, DevOps and cloud-native tools.

Quick links

About

Contribute

Found a great developer tool? Share it with the community.

Submit a tool
© 2026 baike.dev Developer EncyclopediaUpdated daily · Discover great developer tools