Entdecke die libuv-Phasen, den echten Unterschied zwischen setImmediate und setTimeout und wie du eine blockierte Event-Loop erkennst.
Diese Lektion in Kodokon öffnenNode.js überlässt seine Event-Loop libuv, das sie in klar getrennte Phasen gliedert: timers (setTimeout, setInterval), pending callbacks, idle/prepare (intern), poll (I/O: Sockets, Dateien), check (setImmediate) und close callbacks. Bei jedem Tick durchläuft die Loop diese Phasen in genau dieser Reihenfolge. Ein Detail, das wenige Entwicklerinnen und Entwickler kennen: Zwischen zwei Callbacks leert Node zuerst die Warteschlange von process.nextTick und danach die Promise-Microtasks - und das seit Node 11 nach jedem Callback, nicht mehr nur zwischen den Phasen.
import { readFile } from "node:fs";
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
readFile(new URL(import.meta.url), () => {
setTimeout(() => console.log("io timeout"), 0);
setImmediate(() => console.log("io immediate"));
});Beim Start des Skripts ist die Reihenfolge zwischen setTimeout(fn, 0) und setImmediate(fn) nicht deterministisch: setTimeout(fn, 0) wird in Wirklichkeit auf 1 ms angehoben, und wenn die Loop die Phase timers in weniger als einer Millisekunde erreicht, ist der Timer noch nicht fällig. Innerhalb eines I/O-Callbacks befindest du dich dagegen in der Phase poll: Die Phase check (setImmediate) kommt immer vor der Rückkehr zur Phase timers. Deshalb wird io immediate zuverlässig vor io timeout ausgegeben.
setTimeout(() => {
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("promise"));
queueMicrotask(() => console.log("microtask"));
console.log("sync");
}, 0);
// sync, nextTick, promise, microtaskEin feiner Fallstrick: Auf der obersten Ebene eines ES-Moduls dreht sich diese Reihenfolge um - die Promise-Microtasks laufen vor process.nextTick. Der Grund: Die Auswertung eines ES-Moduls wird selbst als Promise-Job ausgeführt, die Microtask-Warteschlange wird also bereits geleert, wenn dein synchroner Code endet. In CommonJS behält nextTick seine Priorität auch auf der obersten Ebene. Prüfe eine solche Reihenfolge immer in dem Modulformat, das du tatsächlich ausrollst.
Die Event-Loop läuft auf einem einzigen Thread: Jede lange synchrone Berechnung - ein JSON.parse auf einer großen Payload, ein katastrophaler regulärer Ausdruck, eine synchrone Verschlüsselung - friert alle laufenden Anfragen ein. Miss in der Produktion die Latenz der Loop mit monitorEventLoopDelay: Ein p99 über einigen zehn Millisekunden deutet auf eine Blockade hin. Der folgende Code löst absichtlich eine Blockade aus und misst sie anschließend.
import { monitorEventLoopDelay }
from "node:perf_hooks";
const h = monitorEventLoopDelay({ resolution: 10 });
h.enable();
const end = Date.now() + 200;
while (Date.now() < end) {}
setTimeout(() => {
h.disable();
const ms = h.max / 1e6;
console.log(`max delay: ${ms.toFixed(1)} ms`);
}, 300);