JAVASCRIPT EVENT LOOP MANTIĞINI ANLAMAK

JavaScript tek iş parçacıklıdır (single-threaded). Yani aynı anda tek satır kod çalıştırır. Ama tarayıcıda yüzlerce işlem aynı anda oluyormuş gibi hissedilir; bir buton tıklanır, bir AJAX cevap döner, bir setInterval çalışır. Bu görünür çoklu paralel davranış event loop sayesinde mümkün olur.
1. JavaScript tek iş parçacıklı mı, paralel mi?
Bu sık karıştırılır. JavaScript dili tek iş parçacıklıdır; ama JavaScript çalışma ortamı (tarayıcı veya Node.js) çok parçalıdır. Mekanizmanın tarayıcı tarafındaki ayrıntıları için web teknolojileri dokümantasyonu derinlemesine bir başvuru sunar. Yani:
- Sizin kodunuz tek bir thread'de çalışır
- Tarayıcı API'ları (setTimeout, fetch, DOM events) ayrı thread'lerde işler
- İşi biten API çıktısını queue'lara yerleştirir
- Event loop bu queue'ları izleyip uygun zamanda main thread'e taşır
Bu yapı sayesinde uzun süren bir işlem (örneğin 500 ms AJAX) main thread'i durdurmaz; sonuç hazır olunca event loop sırasıyla yürütür.
2. Call Stack: sırayla çalışan defter
Call stack, çalışmakta olan fonksiyonların yığını. JavaScript bir fonksiyona girdiğinde stack'in üstüne ekler; fonksiyon bitince stack'ten çıkarır.
function a() {
console.log("a başladı");
b();
console.log("a bitti");
}
function b() {
console.log("b başladı");
c();
console.log("b bitti");
}
function c() {
console.log("c");
}
a();Bu kodun call stack davranışı:
- a() çağrılır → stack: [a]
- "a başladı" yazılır
- b() çağrılır → stack: [a, b]
- "b başladı" yazılır
- c() çağrılır → stack: [a, b, c]
- "c" yazılır, c biter → stack: [a, b]
- "b bitti" yazılır, b biter → stack: [a]
- "a bitti" yazılır, a biter → stack: []
Çıktı sırası: a başladı, b başladı, c, b bitti, a bitti. Bu senkron akış. JavaScript bu akışı bekler; stack temizlenene kadar başka bir şey yapamaz.
3. Task Queue: bekleyen işler kuyruğu
setTimeout, setInterval, DOM events, fetch (sonucu) gibi asenkron işler bittiğinde callback'leri task queue'ya yerleştirilir. Event loop call stack'i izler; stack boşaldığı anda queue'dan sıradaki callback'i alır ve stack'e koyar.
console.log("1");
setTimeout(() => {
console.log("3");
}, 0);
console.log("2");Beklenen sıra: 1, 2, 3. setTimeout 0 milisaniye olsa bile callback hemen çalışmaz; task queue'ya gider, call stack temizlenince çalışır. Bu nedenle "2" "3"ten önce yazılır.
4. Microtask Queue: öncelikli kuyruk
Promise callback'leri, queueMicrotask, MutationObserver microtask queue'ya gider. Bu kuyruk task queue'dan önceliklidir. Event loop her döngüde önce tüm microtask'leri çalıştırır, sonra bir task'i alır.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");Çıktı sırası: 1, 4, 3, 2. Neden?
- "1" senkron yazılır
- setTimeout task queue'ya gider
- Promise.then microtask queue'ya gider
- "4" senkron yazılır
- Call stack temizlenir
- Event loop önce microtask'leri kontrol eder → "3" yazılır
- Sonra task'leri kontrol eder → "2" yazılır
Bu öncelik farkı bug yakalamak için kritiktir. "Neden promise her zaman setTimeout'tan önce çalışıyor?" sorusunun cevabı budur.

5. async / await aslında nedir?
async/await syntax şekerdir; altta promise vardır. Bir fonksiyonun başına async yazmak, fonksiyonun otomatik olarak promise döndürmesine yol açar. await ifadesi ise "burada beklet" der; ama JavaScript bekletmek için thread'i durdurmaz, fonksiyonu duraklatır ve geri kalanı microtask queue'ya alır.
async function fetchData() {
console.log("1");
const veri = await fetch("/api");
console.log("3");
return veri;
}
fetchData();
console.log("2");Çıktı sırası: 1, 2, (sonra fetch tamamlanınca) 3. "await" satırına gelince fonksiyon duraklar; main thread devam eder ("2" yazılır); fetch sonucu microtask olarak gelir ve "3" yazılır.
6. Tipik event loop hataları
1. for döngüsünde setTimeout sürprizi
// var ile
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Çıktı: 3, 3, 3 (beklenen 0, 1, 2 değil)
// let ile
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Çıktı: 0, 1, 2var function-scoped olduğu için döngü bittiğinde i = 3 olur ve tüm setTimeout'lar bu değeri görür. let block-scoped olduğu için her döngüde yeni bir i değişkeni oluşur; setTimeout doğru değeri yakalar.
2. Yoğun hesap main thread'i bloke eder
// Bu kod tarayıcıyı dondurur
for (let i = 0; i < 1_000_000_000; i++) {
// ağır işlem
}
// Sırada bekleyen event'ler (tıklama, animasyon) bloke olurÇözüm: işlemi parçalara böl, setTimeout veya requestAnimationFrame ile arada main thread'e nefes aldır. Veya Web Worker kullan; ayrı thread'de hesaplama yapar.
3. Promise zincirinin sırası
Promise.resolve()
.then(() => {
console.log("A");
return Promise.resolve();
})
.then(() => console.log("B"));
Promise.resolve()
.then(() => console.log("C"))
.then(() => console.log("D"));
// Çıktı: A, C, B, DMicrotask queue'ya promise'lar sırayla eklenir. İlk .then içinde return Promise.resolve() yeni microtask oluşturur; bu nedenle B beklenenden geç gelir.
7. Devtools ile event loop debug
Chrome DevTools'ta event loop sorunlarını ayıklamak için:
Performance paneli
Performance > Record > sayfayı kullan > Stop. Tüm task ve microtask'ler zaman çizgisinde görünür. Uzun süren task'ler kırmızı işaretlenir ("long task"); bunlar main thread'i bloke ediyor demektir.
Async stack traces
DevTools > Settings > Async stack traces aktif. Promise içindeki bir hata oluştuğunda promise zincirini en başa kadar görebilirsiniz. Bu olmadan asenkron hataları takip etmek çok zordur.
Breakpoint with async
Sources panelinde bir promise .then içine breakpoint koyup "Caught/Uncaught" seçeneğini açın. Promise reject olunca otomatik durur.
8. Performance ipuçları
- Long task'leri parçalayın. 50 ms'i geçen task INP metriğini bozar; kullanıcı tıkladığında sayfa donmuş hissi verir
- requestIdleCallback kullanın. Acil olmayan işleri tarayıcı boşken yapın
- Microtask spam'inden kaçının. Promise zincirinin sonsuz uzaması microtask queue'yu doldurur
- Web Worker'a taşıyın. Ağır hesaplar (görsel işleme, encrypt, parse) main thread'ten ayrılmalı
- setInterval yerine setTimeout zinciri. setInterval önceki çağrı bitmese bile tetikler; setTimeout zinciri kontrollü
9. Sık karşılaşılan event loop soruları
setTimeout(fn, 0) tam olarak ne yapar?
Fonksiyonu task queue'ya yerleştirir; en az 0 ms sonra çalıştırılır. "En az" anahtar kelime; tarayıcılar bazen 4 ms gibi minimum gecikme uygular. Pratikte 0 yazmak "mümkün olan en yakın task slotu" anlamına gelir.
Promise içindeki await ne kadar süre bekler?
Await değişken; await edilen şey hazır olunca devam eder. Ama event loop perspektifinden: await olduğu anda fonksiyon duraklar, main thread devam eder; await edilen promise resolve olunca microtask olarak yerleştirilir.
queueMicrotask ne için kullanılır?
Bir kodun mevcut senkron işlerden sonra ama task queue'dan önce çalışmasını istediğinizde. Promise.resolve().then() ile aynı etki ama daha açık niyet.

10. Pratik uygulama: kullanıcı deneyimi
Event loop'u anlamak teorik bir konu değil, pratik UX iyileştirme aracıdır:
- Kullanıcı bir butona tıklayınca anında geri bildirim alır (call stack hemen tepki verir)
- Sayfa ağır bir işlem yaparken donmaz (uzun işler parçalanır)
- Animasyonlar takılmaz (microtask priority kullanılır)
- Form gönderimi sırasında kullanıcı diğer alanları gezebilir (asenkron yapı)
Bu davranışların hiçbiri kendiliğinden gelmez; event loop'a uygun kod yazılınca gelir.
11. Pratiği Geliştirmek
Event loop, JavaScript'in en derin konularından biri. Bir kez anlaşıldığında asenkron kod, promise hataları ve performans sorunları çok daha şeffaf hale gelir. Sistemli bir öğrenme için JavaScript eğitim programı asenkron programlama ve event loop konularını uygulamalı örneklerle aktarır.
12. Süreç Akışı
JavaScript event loop; call stack, task queue ve microtask queue arasındaki sırayı yöneten mekanizmadır. JavaScript tek iş parçacıklıdır ama tarayıcı API'ları paraleldir; event loop bu iki dünyayı senkronize eder. Microtask'ler task'lerden önce çalışır; bu nedenle promise her zaman setTimeout'tan önce gelir. async/await syntax altta promise'lar üzerine kurulu; await ifadesi fonksiyonu duraklatır ama thread'i bloke etmez. Bu mantığı kavramak hem zor bug'ları çözmek hem de yüksek performanslı uygulama yazmak için zorunlu temeldir.
Sıkça Sorulan Sorular
JavaScript multithreaded olamaz mı?
Dilin kendisi tek iş parçacıklıdır. Ama Web Worker API ile arka planda ayrı thread'lerde kod çalıştırmak mümkündür. Web Worker'lar main thread ile sadece postMessage üzerinden iletişim kurar; DOM'a erişimi yoktur. Ağır hesaplar (görsel işleme, encrypt, JSON parse, AI inference) Web Worker'a taşınır. Tarayıcı içinde gerçek paralel hesaplama için tek yol budur.
Microtask ve task arasındaki fark nedir?
İkisi de event loop tarafından işlenir ama farklı önceliklidir. Microtask: Promise.then, <strong>queueMicrotask</strong>, MutationObserver callback'leri. Task: setTimeout, setInterval, DOM event, fetch sonucu callback'leri. Event loop her döngüde önce tüm microtask'leri tüketir, sonra bir task alır. Bu yüzden Promise her zaman setTimeout 0'dan önce çalışır.
setTimeout 0 milisaniye verince neden hemen çalışmıyor?
setTimeout callback'i <strong>task queue</strong>'ya yerleştirilir; queue'ya alındığı anda mevcut senkron kod devam eder. Call stack boşalana kadar task queue'dan callback alınmaz. Hatta tarayıcılar minimum gecikme uygular (genelde 4 ms). Yani 0 ms gerçekte 'en az 0' anlamına gelir; pratik karşılığı 'mevcut senkron işler bitince ilk fırsatta'.
for döngüsünde setTimeout neden hep aynı sayıyı yazar?
var anahtar kelimesi function-scoped'tur; tüm setTimeout callback'leri aynı i değişkenine referans verir. Döngü bittiğinde i son değerine ulaşmış olur (örn. 3) ve tüm callback'ler bu son değeri görür. Çözüm: var yerine let kullanmak. let block-scoped olduğu için her iterasyonda yeni bir i oluşur; her callback kendi yakaladığı değeri görür.
Web Worker hangi durumda gerekli?
Main thread'i 100 ms'ten fazla bloke edecek hesaplar için gerekli. Tipik örnekler: büyük JSON parse, görsel işleme (filter, resize), encrypt/decrypt, çok büyük array sıralama, AI model inference, video frame analizi. Tıklama, hover, animasyon gibi hızlı işler için Web Worker overkill; küçük hesaplar main thread'de kalmalı. Karar verirken kuralı şöyle uygulayın: 'Bu kod tek başına 50 ms'yi geçer mi?' geçiyorsa worker'a taşı.
async/await kullanmak event loop'u değiştirir mi?
Hayır, sadece syntax şekeridir. Altta Promise vardır. await ifadesi olduğu yerde fonksiyon duraklar ve geri kalan kısmı microtask olarak yerleştirilir. Yani 'await' tüm sayfayı durdurmaz; sadece o fonksiyonu duraklatır. Bu yapı sayesinde asenkron kod senkron gibi okunabilir hale gelir ama altta event loop aynı şekilde çalışır.
Long task ne demek ve neden zararlı?
50 ms'i geçen bir task long task olarak işaretlenir. Bu süre boyunca main thread bloke olur; kullanıcı tıklar ama tepki almaz, animasyon takılır, scroll donar. Modern Core Web Vitals metriği INP (Interaction to Next Paint) tam bu davranışı ölçer. Long task'leri kısa parçalara bölmek (setTimeout veya scheduler.postTask ile) UX'i belirgin şekilde iyileştirir ve SEO puanı yükseltir.


