Event Loop je základní mechanismus, který umožňuje Node.js provádět asynchronní operace. Jde o nekonečnou smyčku, která zpracovává úlohy z fronty a běží tak dlouho, dokud jsou v ní nějaké naplánované tasks.
Proč je to důležité:
Jeden sdílený thread
Node.js vykonává uživatelský kód v jednom vlákně. Blokující operace (filesystem, kryptografie) běží v podpůrných vláknech (thread pool).
Run-to-completion
Kód vždy doběhne do konce - díky tomu nevznikají synchronizační problémy typické pro multithreading.
Event Loop Starvation
Zahlcení event loop příliš mnoha asynchronními operacemi - loop nestíhá zpracovávat.
Zablokování
Náročné výpočty v hlavním vlákně zablokují celý event loop.
Jak se bránit zablokování
Chunking - rozdělení dlouhých operací na menší části s <inline-code>setImmediate()<inline-code> mezi nimi
Worker Threads - paralelní JS execution v rámci jednoho procesu (stabilní od Node.js v12 LTS)
Každý worker má vlastní event loop a V8 instance
Komunikace: <inline-code>postMessage()<inline-code> (kopíruje data) nebo <inline-code>SharedArrayBuffer<inline-code> (sdílená paměť)
<inline-code>Atomics<inline-code> pro synchronizaci při sdílené paměti
Child Process / Cluster - oddělené procesy (vyšší izolace, ale větší overhead)
Proces má vlastní alokovanou paměť, vlákno sdílí paměť s rodičovským procesem
Proto je vytvoření procesu náročnější než vlákna
Komunikace s workers:
<inline-code>postMessage()<inline-code> - jednodušší, data se kopírují (structured clone), vhodné pro menší zprávy
<inline-code>SharedArrayBuffer<inline-code> - sdílená paměť bez kopírování, vyžaduje <inline-code>Atomics<inline-code> pro synchronizaci, vhodné pro velké datasety nebo častou komunikaci
// postMessage - jednodušší, kopíruje data
worker.postMessage({ type: 'process', data: myArray });
worker.on('message', (result) => console.log(result));
// SharedArrayBuffer - sdílená paměť, bez kopírování
const shared = new SharedArrayBuffer(1024);
const arr = new Int32Array(shared);
worker.postMessage({ buffer: shared });
// Worker může přímo číst/zapisovat do arr
// Atomics.add(arr, 0, 1) - thread-safe incrementImplementace

Macrotasks
Microtasks (vyšší priorita)
nextTick queue (nejvyšší priorita)
Klíčové pravidlo: Mezi fázemi event loop se nejprve vyprázdní nextTick queue, pak microtask queue, a teprve potom se pokračuje další fází.
macrotask₁ → nextTick queue → microtask queue → macrotask₂ → ...
Toto platí i mezi fázemi event loop - obě fronty mají vždy přednost před macrotasks.
Promise se dostane do event loop až v momentě resolve/reject, ne když je pending.
Fáze Event Loop

Poznámky k exit fázi:
nextTick a Microtasks se zpracovávají:
Obě fronty (nextTick → microtasks) se vždy kompletně vyprázdní před pokračováním
Důležité detaily:
Změna v Node.js 20+ (libuv 1.45.0)
⚠️ Breaking change: Toto může ovlivnit timing aplikací
Co se změnilo: Fáze zůstávají stejné (timers v timers fázi, <inline-code>setImmediate<inline-code> v check fázi). Změnilo se, kdy se timers kontrolují, zda už vypršely.
Před Node.js 20:

Od Node.js 20:

Důsledek: Pod zátěží (zahlcená poll fáze) mohou timers čekat déle než dříve. Řešení: nepoužívat <inline-code>setTimeout(fn, 0)<inline-code> pro time-sensitive operace, monitorovat event loop.
Monitoring event loop
APM nástroje sledují event loop lag automaticky:
Pro ruční měření: <inline-code>perf_hooks.monitorEventLoopDelay()<inline-code>
Příklad 1: setTimeout vs setImmediate
console.log('start');
const immediate = setImmediate(() => console.log('immediate'));
setTimeout(() => {
console.log('timeout');
clearImmediate(immediate);
}, 0);
console.log('end');Výstup
<inline-code>start<inline-code>, <inline-code>end<inline-code>, pak buď <inline-code>timeout<inline-code> nebo <inline-code>immediate, timeout<inline-code> - záleží na tom, zda je timeout připraven při vstupu do loop.
Příklad 2: Event Loop Starvation
async function main() {
console.log('start');
let run = true;
setTimeout(() => {
console.log('timeout');
run = false;
}, 0);
while (run) {
await Promise.resolve();
}
}
main();
Výstup
<inline-code>start<inline-code> a pak... nic. Nekonečný loop!
<inline-code>await Promise.resolve()<inline-code> je microtask. Microtasks mají vyšší prioritu a zpracovávají se kompletně po každém macrotask. Timeout (macrotask) se nikdy nedostane na řadu, protože while loop neustále přidává nové microtasks.
Event loop = srdce Node.js - pochopení je klíčové pro psaní výkonného kódu
Run-to-completion - callback vždy doběhne bez přerušení, žádné race conditions uvnitř
nextTick > Microtasks > Macrotasks - nextTick queue se zpracuje první, pak promises
Neblokuj hlavní vlákno - dlouhé výpočty přesuň do worker threads
Worker threads efektivně - používej worker pool (ne per-task), počet ≈ CPU jader, pro velká data <inline-code>SharedArrayBuffer<inline-code>
Pozor na starvation - <inline-code>process.nextTick<inline-code> a nekonečné async smyčky mohou zablokovat vše ostatní
libuv dokumentace
Node.js Event Loop docs
Clinic.js - diagnostika výkonu Node.js
HTML5 Event Loop spec
Bun runtime
Deno runtime
P.S. Pokud provozujete Node.js v produkci a chcete se vyhnout nepříjemným výkonovým překvapením, rádi vám pomůžeme. Ozvěte se nám, pokud chcete druhý názor nebo jít do hlubší diskuze.