Yazılarımız

OfisData

JAVASCRİPT EVENT LOOP MANTIĞINI ANLAMAK VE HATA AYIKLAMAK

Bazı hatalar vardır: “Bazen oluyor, bazen olmuyor.” Konsolda doğru görünen log’lar, arada bir ters sırada çalışan callback’ler, UI’nin donup sonra toparlanması… Bu tip sorunların çoğu, event loop’un nasıl çalıştığını tam bilmemekten doğar.

JavaScript tek iş parçacığıyla çalışır ama asenkron işleri de aynı anda yürütüyormuş gibi hissettirir. Bu sihir; call stack, task queue ve microtask queue arasındaki düzenle sağlanır. Düzeni bildiğinizde, “neden önce bu çalıştı?” sorusu net bir cevaba kavuşur.

Bu yazıda event loop mantığını sezgisel bir modelle ele alacağız; ardından gerçekçi örneklerle promise, setTimeout ve DOM event’lerinin sırasını görerek hata ayıklama yaklaşımını güçlendireceğiz. Derinleşmek isterseniz javascript eğitimi içeriğinde asenkron programlama ve debugging pratiklerini daha kapsamlı uyguluyoruz.

Bir geliştiricinin editörde JavaScript kodunu ve yanında zaman çizelgesi notlarını karşılaştırarak akışı takip etmesi

Event loop bileşenlerini zihinde modellemek

Event loop’u anlamanın en hızlı yolu, onu bir trafik polisi gibi düşünmektir: sırayla gelen işleri alır, doğru kuyruğa yerleştirir ve uygun zamanda çalıştırır. Buradaki kritik nokta; her işin aynı kuyruğa gitmemesidir.

Call stack üzerinden senkron akışı okumak

Senkron kod, call stack üzerinde çalışır ve bitmeden diğer işlere geçilmez. Bu yüzden uzun süren bir döngü veya bloklayan bir işlem, kullanıcı etkileşimini geciktirebilir. Debugging’de ilk kontrol, “stack’i ne dolduruyor?” sorusuyla başlamalıdır.

Task queue ve microtask queue farkını görmek

Timer’lar (setTimeout), bazı tarayıcı API’leri ve mesajlaşma mekanizmaları genellikle task queue’ya düşer. Promise zincirleri ve queueMicrotask ise microtask queue’ya düşer. Event loop, her “tur” sonunda microtask’leri bitirmeden yeni task’a geçmez; bu detay sıralama hatalarının ana kaynağıdır.


Çalışma sırasını belirleyen kuralları kavramak

“Önce hangisi çalışır?” sorusunu tahminle değil kuralla çözmek gerekir. Bu kurallar, pratikte log sırasını açıklamanın yanı sıra performans sorunlarını da görünür kılar.

Microtask önceliğini pratikte kanıtlamak

Promise callback’leri, mevcut senkron iş bittikten hemen sonra devreye girer; ardından sıradaki task’lar çalışır. Bu yüzden setTimeout(0) “hemen” çalışmaz; çoğu zaman promise then’lerden sonra çalışır. Bu farkı bilmek, “race condition” benzeri hataları hızlı açıklamayı sağlar.

Render ve kullanıcı etkileşimi ile ilişkisini anlamak

Tarayıcı, render için de zaman ister. Microtask’leri arka arkaya zincirlemek, tarayıcının render fırsatını geciktirebilir. Bu da “UI dondu” hissi yaratır. Hata ayıklarken yalnızca fonksiyon sırası değil, render aralıkları da düşünülmelidir.

// Beklenen çıktı sırasını tahmin edin:
console.log("A");

setTimeout(() => {
  console.log("B: timeout");
}, 0);

Promise.resolve()
  .then(() => console.log("C: promise then 1"))
  .then(() => console.log("D: promise then 2"));

console.log("E");

// Tipik çıktı:
// A
// E
// C: promise then 1
// D: promise then 2
// B: timeout

Asenkron hataları yakalamak için debug yaklaşımı kurmak

Event loop kaynaklı hatalar çoğu zaman “yanlış sırada güncelleme” veya “beklenmeyen zamanda state değişimi” olarak görünür. Bu hataları çözmek için sistematik bir debug rutini gerekir.

Log yerine zaman damgalı iz sürmek

Basit console.log çoğu zaman yetmez; çünkü “ne zaman” sorusu kaybolur. Zaman damgası, çağrı kimliği ve işlem adımı eklemek; aynı anda çalışan akışları ayırmayı kolaylaştırır. Bu yaklaşım, özellikle API istekleri ve UI etkileşimi birleştiğinde işe yarar.

Breakpoint ve async stack ile kaynağa inmek

Tarayıcı devtools, async stack izini takip etmeyi destekler. Böylece callback’e nasıl gelindiğini, hangi promise zincirinin tetiklediğini görebilirsiniz. Hata ayıklarken hedef; semptomu değil, işi kuyruğa atan noktayı bulmaktır.

  • Akışı etiketlemek için requestId veya traceId üretmek
  • Zamanlama için performance.now() ile ölçmek
  • Kuyruk türünü ayırt etmek (promise mi timer mı)
  • İptal senaryosu eklemek (abort, flag, cleanup)

Gerçek hayatta görülen senaryoları çözümlemek

Teori, pratikte iki senaryoda karşımıza çıkar: UI güncellemesi ve network sonuçları. İkisi de “önce gelen kazanır” gibi basit bir mantıkla çözülmez; event loop sırası ve state yönetimi belirleyicidir.

Race condition benzeri sorunları azaltmak

Kullanıcı hızlıca filtre değiştirir, iki istek art arda gider ve geç gelen yanıt ekranda kalır… Bu tip durumda sorun event loop değil, event loop üzerinde üst üste binen işlerin yönetilmemesidir. Çözüm; “son istek kazanır” kuralı koymak veya istekleri iptal edebilmektir.

Timer ile promise karışımında sıra hatasını tespit etmek

Bazı kodlar hem setTimeout hem promise kullanır; bu da “bazı cihazlarda farklı sırada” gibi algılanan davranışlara neden olabilir. Çözüm; kritik sırayı tek bir mekanizmaya indirgemek veya açık bir durum makinesiyle ilerlemektir.

// Basit bir "son istek kazanır" yaklaşımı:
let latestRequest = 0;

async function fetchProducts(query) {
  const requestId = ++latestRequest;

  const res = await fetch("/api/products?q=" + encodeURIComponent(query));
  const data = await res.json();

  // Geç gelen istekleri yok saymak:
  if (requestId !== latestRequest) return;

  renderProducts(data);
}

// UI event:
document.querySelector("#search").addEventListener("input", (e) => {
  fetchProducts(e.target.value);
});

Performansı bozan event loop anti-patternlerini fark etmek

Asenkron kod hatası yalnızca sıra ile ilgili değildir; performans da işin içindedir. Özellikle microtask’leri aşırı zincirlemek, render fırsatlarını kısıtlayabilir ve kullanıcı deneyimini zayıflatabilir.

Uzun süren senkron işleri küçük parçalara bölmek

Büyük veri işleme veya yoğun DOM işlemleri, tek parçada yapıldığında call stack’i uzun süre meşgul eder. İşleri parçalara bölmek, kullanıcı etkileşimi ve render için nefes alanı bırakır. Bu yaklaşım, “tıklandı ama tepki vermedi” hissini azaltır.

Microtask selini kontrol altına almak

Promise zincirleri ile art arda mikro işler üretmek, tarayıcının her turda microtask’leri bitirme kuralı nedeniyle render gecikmesine yol açabilir. Kritik olmayan işleri task tarafına taşımak veya araya planlı boşluklar eklemek daha dengeli olur.


Ekiplere uygun bir hata ayıklama standardı oluşturmak

Event loop bilgisi bireysel bir kazanım gibi görünse de, ekip içinde ortak bir dil olduğunda çok daha hızlı sonuç verir. PR yorumlarında “microtask önceliği” veya “stack’i bloklama” gibi net ifadeler, tartışmayı kısaltır.

Paylaşılan kontrol listesiyle üretime daha güvenli çıkmak

Asenkron değişikliklerde basit bir kontrol listesi, regressions riskini düşürür. Özellikle UI güncellemesi, API istekleri ve state yönetimi birleştiğinde kontrol listesi hayat kurtarır.

  1. Sıra bağımlılığı var mı, açıkça yazmak
  2. İptal/cleanup senaryosunu eklemek
  3. Hata yakalama ve fallback akışını planlamak
  4. Performans etkisi için ölçüm eklemek

Özet: JavaScript event loop mantığını bildiğinizde, “rastgele” görünen bug’ların çoğu açıklanabilir hale gelir. Senkron akışı stack üzerinden okumak, microtask ve task kuyruğunu ayırt etmek, render etkisini düşünmek ve iz sürmeyi standartlaştırmak; hata ayıklamayı hızlandırır. Bu pratiği daha fazla örnekle pekiştirmek için javascript eğitimi içeriğinde asenkron akış ve debugging senaryolarını uygulamalı ele alıyoruz.

Devtools ekranında call stack, breakpoints ve async izleme paneli açıkken kod akışının adım adım incelenmesi

 Vimaj