JAVASCRIPT EVENT LOOP NEDİR?

Veri Akademi7 dk okuma
JavaScript event loop merkezi döngü hub'ı ve call stack ile task queue bağlantısı diyagramı

Buton tıklanıyor, ekran kilitleniyor. Kullanıcı bir kez daha basıyor, hâlâ tepki yok. Konsolda hata da yok. Sorun şu: arka planda 4 saniye süren bir hesaplama döngüsü çalışıyor ve tarayıcı bu süre boyunca tek bir tıklamayı dahi işleyemiyor. JavaScript'in tek thread'li yapısı bu kilitlenmenin sebebi; event loop ise neden böyle olduğunun ve nasıl aşılacağının cevabı.

1. Tek Thread Bir Dil, Eşzamanlı Görünen Bir Dünya

JavaScript aynı anda yalnızca tek bir iş yapar. Yani for döngüsü çalışırken başka hiçbir kod, tıklama olayı veya zamanlayıcı geri çağrısı yürütülemez. Buna rağmen bir web sayfasında onlarca şey eşzamanlı oluyormuş gibi görünür: animasyon devam eder, fetch isteği yanıt bekler, kullanıcı yazı yazar. Bu yanılsamanın arkasındaki mekanizma event loop'tur.

Event loop tek başına çalışmaz. Call stack, web API'leri (veya Node tarafında C++ API'leri), task queue ve microtask queue ile birlikte bir orkestra gibi davranır. Her birinin görevi farklıdır.

2. Call Stack: Sıradaki İş Burada Çalışır

Call stack, çalışmakta olan fonksiyonların LIFO mantığıyla tutulduğu yığındır. Bir fonksiyon çağrıldığında stack'e push edilir, return ettiğinde pop edilir. JavaScript motoru her an yalnızca stack'in en üstündeki fonksiyonu yürütür.

  • Stack boş değilse event loop hiçbir şey yapamaz; sıradaki callback'ler beklemek zorundadır.
  • Stack boşaldığında event loop devreye girer ve kuyruklardan yeni bir iş çeker.
  • Stack overflow, sonsuz rekürsiyonun stack'i taşırması durumudur.

Buradan çıkan kritik sonuç: stack'te uzun süren senkron bir iş varsa, tarayıcı diğer her şeyi (render, click, scroll) bekletmek zorunda kalır.

3. Long-Running Sync Function UI'yi Neden Dondurur

Şu örneği düşünün:

button.addEventListener("click", () => { let sum = 0; for (let i = 0; i < 1e10; i++) sum += i; });

Tıklamadan sonra döngü tamamlanana kadar call stack bu callback ile dolu kalır. Bu sırada:

  1. Tarayıcı yeni tıklamaları algılar ama callback'lerini çalıştıramaz; kuyruğa atar.
  2. Repaint adımı atlanır — animasyonlar donar.
  3. Kullanıcı "sayfa yanıt vermiyor" uyarısıyla karşılaşır.

Çözüm callback'i parçalamak veya işi web worker gibi farklı bir thread'e devretmektir. Asenkron API'lerin var olma nedeni tam olarak budur.

Call stack ve task queue arasında event loop'un callback aktarımını gösteren akış diyagramı

4. Web API'leri ve Task Queue

setTimeout, fetch, DOM event listener gibi işler aslında JavaScript motorunun değil, tarayıcının (veya Node ortamının) sorumluluğundadır. Bir setTimeout(fn, 1000) çağrıldığında:

  • Timer tarayıcının web API katmanına teslim edilir.
  • JavaScript motoru hemen bir sonraki satıra geçer; bekleme yapmaz.
  • Süre dolduğunda callback, task queue'ya (macrotask queue) eklenir.
  • Event loop, call stack boşaldığında bu callback'i alır ve stack'e push eder.

Bu yüzden setTimeout(fn, 0) "hemen çalıştır" demek değildir; "stack boşalır boşalmaz, ondan sonra çalıştır" demektir.

5. Microtask Queue: Promise'ler Burada Yaşar

Microtask queue, task queue'dan farklı ve ondan önceliklidir. Promise.then, queueMicrotask, MutationObserver callback'leri buraya düşer. Event loop'un altın kuralı:

Bir task bittiğinde, sıradaki task'a geçmeden önce microtask queue tamamen boşaltılır. Bu, peş peşe zincirlenmiş promise'lerin neden render adımları arasına sıkışmadan çalıştığını açıklar. Kuyruk önceliklerinin ve task sınıflandırmasının formal tanımları için resmi dokümantasyonu incelemek faydalı olur.

Pratik sonuç: microtask queue'da kendini sürekli besleyen bir zincir oluşturursanız (her .then yenisini eklerse) tarayıcı asla render edemez. Buna "microtask starvation" denir.

6. Render Adımı Nereye Sığıyor?

Tarayıcı her task arasında potansiyel olarak bir render adımı çalıştırır: style hesabı, layout, paint, composite. Ancak render zorunlu değildir — tarayıcı genelde saniyede 60 kez fırsat kollar. Eğer task'lar arası boşluk yoksa render adımı atlanır ve sayfa "donmuş" gibi görünür.

requestAnimationFrame, callback'i bir sonraki render'dan hemen önce çalıştırır; bu yüzden animasyonlar için setTimeout yerine tercih edilir.

Microtask ve macrotask kuyrukları ile render adımının event loop önceliğine göre sıralanması

7. Pratikte Ne Yapmalı?

  • Ağır hesaplamayı böl: requestIdleCallback veya setTimeout ile parçala, her iterasyon sonunda kontrolü event loop'a iade et.
  • Web Worker kullan: Saf hesaplama yükünü ayrı thread'e taşı; ana thread render için boş kalsın.
  • Promise zincirinden kaçma: Microtask starvation riskini fark et, gerektiğinde setTimeout(fn, 0) ile macrotask sınırına geç.
  • DevTools Performance sekmesi: Long task'ları (50ms+) görmek için kullan; hangi fonksiyonun stack'i tıkadığını bulursun.

Asenkron JavaScript'in tüm mekaniğini ve callback / promise / async-await arasındaki ayrımı pekiştirmek için JavaScript eğitimi içeriğinden yararlanabilirsiniz; event loop bilgisi, bu konuların hepsinin altında yatan temeldir.

8. Node.js Tarafında Küçük Bir Not

Node.js, libuv üzerine kurulu farklı bir event loop modeline sahiptir: timers, pending callbacks, poll, check, close gibi fazlardan oluşur. setImmediate ile setTimeout(fn, 0) arasındaki ince farklar bu fazlardan kaynaklanır. Tarayıcı modelini bilen bir geliştirici Node tarafına geçtiğinde benzer mantıkla ama farklı isimlendirmelerle karşılaşır; ana fikir aynı kalır: stack boşalmadan callback çalışmaz.

Event loop kavramını içselleştirmek, "neden bu fetch sonucu beklenenden geç geldi", "neden bu animasyon takıldı", "neden async/await sırası bu" gibi sorulara doğrudan yanıt verir. Tek thread'li bir dilin nasıl olup da bu kadar çok şeyi aynı anda yapıyormuş gibi göründüğünün cevabı, küçük ama disiplinli bir döngüde gizli.

Sıkça Sorulan Sorular

Event loop tam olarak ne yapar?

Event loop, call stack'in boş olup olmadığını sürekli kontrol eden ve boşaldığında microtask ile task queue'lardan yeni callback'leri stack'e taşıyan döngüdür. Kendisi bir kod yürütmez; sadece zamanlama ve aktarım görevi yapar. JavaScript'in tek thread'li yapısına rağmen asenkron işleyiş bu sayede mümkündür.

setTimeout(fn, 0) neden hemen çalışmaz?

Çünkü fn, doğrudan çağrılmaz; tarayıcının web API katmanına gönderilir ve 0 ms gecikmenin ardından task queue'ya eklenir. Event loop ancak call stack tamamen boşaldıktan sonra kuyruktan bu callback'i alır. Stack'te uzun bir iş varsa 0 ms gecikmeli timer bile saniyelerce bekleyebilir.

Microtask ile macrotask arasındaki fark nedir?

Macrotask kuyruğunda setTimeout, setInterval, DOM event callback'leri bulunur. Microtask kuyruğunda ise Promise.then, queueMicrotask ve MutationObserver callback'leri vardır. Event loop her task'tan sonra microtask kuyruğunu tamamen boşaltır, bu yüzden microtask'lar macrotask'lardan daha önceliklidir.

Long-running sync function UI'yi neden dondurur?

Çünkü senkron kod call stack'i meşgul tutar ve stack boşalmadan event loop yeni iş alamaz. Bu süre boyunca tıklama callback'leri, animasyon repaint'i ve scroll olayları beklemek zorunda kalır. Sonuçta tarayıcı kullanıcının girdilerine tepki veremez ve sayfa donmuş gibi görünür.

Microtask starvation nedir?

Bir microtask'ın kendi kuyruğuna sürekli yeni microtask eklemesiyle oluşan durumdur. Kuyruk hiç boşalmadığı için event loop bir sonraki macrotask'a veya render adımına geçemez. Bu durumda timer'lar gecikir, sayfa donar. Çözüm, gerektiğinde setTimeout ile akışı macrotask sınırına taşımaktır.

Ağır bir hesaplamayı UI'yi dondurmadan nasıl çalıştırırım?

İki ana yol vardır. İlki işi küçük parçalara bölüp her parça arasında setTimeout veya requestIdleCallback ile kontrolü event loop'a iade etmek. İkincisi Web Worker kullanarak hesaplamayı tamamen farklı bir thread'e taşımak; bu sayede ana thread render ve etkileşim için serbest kalır.

requestAnimationFrame setTimeout'tan neden daha iyidir?

requestAnimationFrame callback'i, bir sonraki repaint adımından hemen önce çalışacak şekilde planlar. Bu, tarayıcının render döngüsüyle senkronize çalışır ve gereksiz ara frame'leri önler. setTimeout ise render döngüsünden bağımsız tetiklenir; bu da yırtılmış animasyonlara ve sayfa görünmezken bile çalışmaya yol açar.

Node.js'in event loop'u tarayıcıyla aynı mı?

Genel prensip aynıdır ama uygulama farklıdır. Node libuv üzerine kurulu olup timers, pending callbacks, poll, check ve close gibi belirli fazlardan oluşan bir döngü işletir. setImmediate gibi tarayıcıda olmayan API'leri bu fazlardan biri için tasarlanmıştır; mikrotask önceliği ise her iki ortamda da benzer şekilde çalışır.