Menu

Node.js Event Loop Nedir: Fazlar, nextTick, setImmediate

Node.js event loop callback'lerini fazlar halinde çalıştırır (timers, poll, check, close) ve her callback'ten sonra önce process.nextTick kuyruğunu, sonra promise kuyruğunu boşaltır. Kesin sırayı görmek için örnekleri çalıştır.

Bu sayfada çalıştırılabilir editörler var. Düzenle, çalıştır ve sonucu anında gör.

Node.js event loop, senkron kodun bittikten sonra sıradaki callback'in hangisi olacağına karar veren döngüdür. Sabit bir döngüde fazlardan geçer: timers (setTimeout, setInterval), pending callbacks, poll (dosya okuma ve ağ verisi gibi I/O), check (setImmediate) ve close callbacks. Her callback'ten sonra Node önce process.nextTick kuyruğunu, sonra promise kuyruğunu boşaltır, yani bunlar döngü ilerlemeden önce her zaman çalışır.

Çıktı sync, nextTick, promise, setTimeout olur, yani yazıldıkları sıranın tersi. Senkron kod her zaman önce biter, sonra Node nextTick kuyruğunu, ardından promise kuyruğunu boşaltır ve ancak bundan sonra döngü başlayıp timers fazına ulaşır.

Event loop'un fazları

Node'un döngüsü, Node'un I/O için kullandığı C kütüphanesi libuv'den gelir. İlk turdan önce zamanı gelmiş timer'ları çalıştırır, ardından döngünün her turu şu fazları sırayla çalıştırır:

  1. Pending callbacks: önceki turdan ertelenmiş birkaç sistem callback'i, örneğin bazı ağ hataları.
  2. Poll: yeni I/O olayları ve callback'leri (bir dosya okuması bitti, bir sokete veri geldi). Başka planlanmış bir şey yoksa döngü burada bekler.
  3. Check: setImmediate callback'leri.
  4. Close callbacks: yok edilmiş bir soket gibi 'close' olayları.
  5. Timers: zamanı gelmiş setTimeout ve setInterval callback'leri.

Birçok rehber timers fazını her turun başında gösterir, Node 20 sürümünden (libuv 1.45) önce böyle çalışıyordu. Callback'ler iki durumda da aynı sırayla çalışır: bir turun timer'larından sonra bir sonraki turun poll fazı gelir.

İki kuyruk fazların dışında durur: nextTick kuyruğu ve microtask kuyruğu (promise callback'leri ve queueMicrotask). Node ikisini de her fazda, her bir callback'ten sonra boşaltır, önce nextTick. Bekleyecek bir şey olduğu sürece (bir timer, açık bir dosya ya da soket, bir sunucu) döngü dönmeye devam eder ve hiçbir şey kalmadığında süreç sonlanır.

Bu Node'un kendi döngüsü. Tarayıcı görev ve microtask fikrini paylaşır ama fazları, setImmediate'i ve process.nextTick'i yoktur; dil düzeyindeki resmi JavaScript event loop sayfası anlatıyor.

process.nextTick ve promise'ler

İkisi de döngü devam etmeden önce çalışır ama birbirine karışmaz. Node tüm nextTick kuyruğunu, çalışırken eklenen tick'ler dahil, sonuna kadar çalıştırır ve ancak ondan sonra promise kuyruğuna geçer. Bunun sırasını tahmin et:

Sıra şöyle: tick 1, tick 2, tick from tick, promise 1, promise 2. Bir tick'in içinde planlanan tick aynı kuyruğa katılır ve promise önce oluşturulmuş olsa bile yine her promise'ten önce çalışır.

Bir ES modülünün en üst seviyesinde (import kullanan bir dosya) bu ikisi yer değiştirir: modülün kodu zaten bir promise işinin içinde çalışır, bu yüzden promise callback'leri Node nextTick kuyruğuna gelmeden biter. Callback'lerin içinde ise önce nextTick kuralı iki modül sisteminde de geçerlidir.

setImmediate ve setTimeout(fn, 0)

setTimeout(fn, 0) aslında "en az 1 ms sonra, timers fazında" demektir. setImmediate(fn) ise "bir sonraki check fazında" demektir. Hangisinin önce çalışacağı onları nerede çağırdığına bağlıdır.

Bir I/O callback'inin içinde sıra sabittir. Callback poll fazında çalışır, check fazı hemen ardından gelir ve timers fazı ancak ondan sonra gelir:

Her seferinde nextTick, setImmediate, setTimeout yazdırır. Bu yüzden "bunu mevcut I/O işinden sonra çalıştır" için doğru seçim setImmediate'tir.

Ana modülde, herhangi bir callback'in dışında, ikisinin sırası sabit değildir:

// Order depends on how fast the process started
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));

Döngü başladığında en az 1 ms geçmişse timer'ın zamanı gelmiştir ve önce o yazdırılır; geçmemişse setImmediate kazanır. Bu sonuçlardan birine dayanan kod yazma.

Ne zaman çalışırNe için kullanılır
process.nextTick(fn)Mevcut işlemden hemen sonra, promise'lerden önceÇağıran listener'larını ekledikten sonra bir olay ya da hata yaymak
Promise.then, queueMicrotasknextTick kuyruğu boşaldıktan sonraSıradan async kod
setImmediate(fn)Check fazında, I/O callback'lerinden sonraİş parçaları arasında döngüye sıra vermek
setTimeout(fn, 0)Timers fazında, en az 1 ms sonraKesin anın önemli olmadığı gecikmeler

Event loop'u bloklamak

Tek bir JavaScript thread'i olduğu için uzun süren bir callback diğer her şeyi durdurur: timer'ları, I/O callback'lerini ve bir sunucuda diğer bütün istekleri. Bir timer'ın gecikmesi bir alt sınırdır, garanti değil.

Önce loop finished, sonra true yazdırılır: timer'ın zamanı 10 ms sonra gelmişti ama meşgul döngü thread'i geri verene kadar çalışamadı. Bir sunucuda aynı döngü bir route handler'ın içinde olsaydı diğer bütün istekleri yarım saniye dondururdu.

Uzun işler için çözüm, işi bölmek ve parçalar arasında setImmediate ile döngüye bir tur vermektir:

Timer 1. parçadan sonra araya girer. setImmediate(() => work(part + 1)) yerine work(part + 1) yaz ve tekrar çalıştır: üç parça da tek seferde çalışır ve timer en son yazdırılır.

Görüntü işleme gibi baştan sona ağır işlerde bölmek yetmez: o işi worker_threads ile ana thread'in dışına taşı.

Sık yapılan hatalar

process.nextTick'i özyinelemeli çağırmak. Sürekli yeni bir nextTick planlayan bir nextTick callback'i döngünün devam etmesine hiç izin vermez, böylece timer'lar ve I/O aç kalır. Tekrarlanan işler için setImmediate kullan.

Bir sunucuda senkron API'ler kullanmak. fs.readFileSync, crypto.pbkdf2Sync ve büyük JSON.parse çağrıları döngüyü her kullanıcı için bloklar. İstek handler'larının içinde async sürümlerini kullan.

setTimeout(fn, 0)'ın hemen çalışmasını beklemek. Tüm senkron koddan, her nextTick'ten ve her promise callback'inden sonra çalışır, o da ancak döngü timers fazına ulaştığında.

Sıkça Sorulan Sorular

Node.js tek thread'li mi?

JavaScript kodun tek bir thread üzerinde çalışır, bu yüzden iki callback asla aynı anda çalışmaz. Node'un kendisi daha fazla thread kullanır: libuv dosya sistemi işleri, DNS sorguları ve bazı kripto işlemleri için bir havuz tutar (varsayılan olarak dört thread) ve sonuçları döngüye geri verir.

Bir Node.js programı ne zaman sonlanır?

Event loop'un bekleyecek hiçbir şeyi kalmadığında: timer yok, bekleyen I/O yok, açık sunucu ya da soket yok. Çalışan bir setInterval ya da dinleyen bir sunucu, sen onu temizleyene ya da kapatana kadar süreci ayakta tutar.

Node.js event loop tarayıcınınkiyle aynı mı?

Fikir aynı, kuyruğa alınmış callback'leri çalıştıran tek bir thread, ama ayrıntılar farklı. Tarayıcıda setImmediate ya da process.nextTick yoktur ve görevlerin arasına render işini sığdırır, Node ise libuv'nin fazlarını çalıştırır. Ortak, dil düzeyindeki kısmı JavaScript event loop sayfası anlatıyor.

Event loop'un bloklandığını nasıl anlarım?

Ne kadar geç çalıştığını ölç: perf_hooks içindeki monitorEventLoopDelay() gecikmeyi kaydeder ve onlarca milisaniyelik bir ortalama, bir callback'in thread'i fazla uzun tuttuğu anlamına gelir. Hangi fonksiyon olduğunu görmek için Node'u --cpu-prof ile çalıştır.

Coddy programlama dilleri çizimi

Coddy ile kodlamayı öğren

Başla