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é:
- Umožňuje concurrency (souběžnost) bez nutnosti více vláken
- Výkon aplikace závisí na tom, jak rychle loop zpracovává tasks - měříme pomocí
perf_hooks - Alternativy v jiných jazycích: více vláken, goroutines (Go), Virtual Threads (Java 21+)
Klíčové vlastnosti a best practices
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 setImmediate() 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:
postMessage()(kopíruje data) neboSharedArrayBuffer(sdílená paměť) Atomicspro 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:
postMessage()- jednodušší, data se kopírují (structured clone), vhodné pro menší zprávySharedArrayBuffer- sdílená paměť bez kopírování, vyžadujeAtomicspro synchronizaci, vhodné pro velké datasety nebo časté komunikace
// 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 increment
Implementace

Event loop není součástí JS enginu (V8/JSC), ale je implementován externě.
Základní pojmy: Macrotasks vs Microtasks
Macrotasks
setTimeoutsetIntervalsetImmediate- I/O operace
Microtasks (vyšší priorita)
- Promises (
.then,.catch,.finally) queueMicrotask
nextTick queue (nejvyšší priorita)
process.nextTick- vlastní oddělená fronta, není to microtask- Zpracovává se PŘED microtask queue
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:
beforeExit- poslední šance naplánovat práci → pokud ano, loop pokračuje od timersexit- pouze synchronní kód, async operace se ignorují- Explicitní
process.exit()přeskočíbeforeExit
nextTick a Microtasks se zpracovávají:
- Mezi fázemi event loop
- Po každém callbacku v rámci fáze (od Node.js 11+)
Obě fronty (nextTick → microtasks) se vždy kompletně vyprázdní před pokračováním.
Důležité detaily:
- Kód se spouští PŘED vstupem do loop
- V každé fázi loop zpracuje všechny tasks (nebo do hard limitu)
- Nový macrotask stejného typu nejde do aktuální iterace
- Poll fáze může blokovat, pokud čeká na I/O a není nic dalšího naplánovaného
- Loop se ukončí, když nejsou žádné aktivní handles, pending requests ani čekající timers
Změna v Node.js 20+ (libuv 1.45.0)
⚠️ Breaking change: Toto může ovlivnit timing aplikací. Fáze zůstávají stejné (timers v timers fázi, setImmediate v check fázi). Změnilo se, kdy se timers kontrolují, zda už vypršely.
Před Node.js 20:

Od Node.js 20:

Pod zátěží (zahlcená poll fáze) mohou timers čekat déle než dříve. Řešení: nepoužívat setTimeout(fn, 0) pro time-sensitive operace, monitorovat event loop.
Monitoring event loop
APM nástroje sledují event loop lag automaticky:
- OpenTelemetry (
@opentelemetry/instrumentation-runtime-node) - Datadog, New Relic, Dynatrace
- Clinic.js pro lokální diagnostiku
Pro ruční měření: perf_hooks.monitorEventLoopDelay()
Praktické příklady (Challenge)
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: start, end, pak buď timeout nebo immediate, timeout - 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();
start a pak… nic. Nekonečný loop! await Promise.resolve() 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.
Shrnutí key takeaways
- 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
SharedArrayBuffer - Pozor na starvation -
process.nextTicka nekonečné async smyčky mohou zablokovat vše ostatní
Užitečné odkazy
- 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.