JAVASCRIPT EVENT LOOP MANTIĞINI ANLAMAK
JavaScript yazan herkesin başına gelir: bir kod yazarsınız, "neden önce bu çalıştı sonra bu?" diye düşünürsünüz. Bir console.log diğerinden önce çıkar; setTimeout 0 milisaniye olsa bile gecikiyormuş gibi davranır; promise resolve olur ama henüz işlem bitmez. Bu davranışların hepsinin altında tek bir mekanizma yatar: event loop.
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; mekanizmanın tarayıcı tarafındaki ayrıntıları için web teknolojileri dokümantasyonu derinlemesine bir başvuru sunar.
Aşağıda event loop'un ne olduğunu, call stack, task queue, microtask queue arasındaki sırayı, promise ve async/await davranışlarını gerçekçi örneklerle açıklıyoruz.
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. 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.
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.
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.
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.

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.
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.
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.
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ü
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.

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.
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.
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.



