JavaScript Olay Döngüsü: Gerçekten Nasıl Çalışır, Çağrı Yığını, Mikro Görev ve Makro Görev
Olay döngüsü JavaScript'in kalbidir ancak geliştiricilerin çoğu bunu anlamaz belirsiz. Tam olarak açıklıyoruz: çağrı yığını, web API'leri, geri arama kuyruğu, mikro görev kuyruğu (Söz), makro görev kuyruğu (setTimeout) ve yürütme sırasının sizi neden şaşırtabileceği.
JavaScript Tek İş Parçacıklıdır: Gerçekte Ne Anlama Gelir?
JavaScript'in var tek bir yürütme iş parçacığı: herhangi bir zamanda yalnızca bir tane JavaScript ifadesi yürütülüyor. Bu bir çalışma zamanı gerçeğidir, bir dil sınırı. JavaScript Motoru (Chrome/Node.js'de V8, Firefox'ta SpiderMonkey) kodu sırayla çalıştırır.
Ancak Node.js on binlerce eş zamanlı isteği işliyor. Gibi? aracılığıyla
olay döngüsü: Asenkron I/O işlemlerini beklemenizi sağlayan bir mekanizma
(ağ, dosya sistemi) iş parçacığını engellemeden, asıl işi işletim sistemine devrederek
aracılığıyla libuv.
Olay Döngüsünün Bileşenleri
Node.js'deki (V8 + libuv) olay döngüsü aşağıdakilerden oluşur:
- Çağrı Yığınları: JavaScript kodunun eşzamanlı yürütme yığını. Ne zaman bir işlev çağrılır ve yığına "itilir"; bittiğinde "beslenir". Yığın boş değilse iş parçacığı meşgul demektir.
-
Web API'leri / Düğüm API'leri: ortam tarafından sağlanan arayüzler (tarayıcı veya Node.js)
eşzamansız G/Ç işlemleri için:
setTimeout,fetch,fs.readFile, WebSockets, vb. Bu işlemler C++ çalışma zamanına (libuv) devredilir ve JS iş parçacığını KULLANMAZ. - Geri Arama Kuyruğu (Makro Görev Kuyruğu): Sonraki yürütülecek geri aramaların sırası G/Ç işlemleri tamamlandı, zaman aşımı, aralık Çağrı yığını boşaldıktan SONRA yürütülür ve mikro görev kuyruğu boşaltılır.
-
Mikro Görev Sırası: Promise geri aramaları için yüksek öncelikli kuyruk
(
.then(),async/await) VequeueMicrotask(). O geliyor her makro görevden ÖNCE tamamen boşaltılır.
İnfaz Sırası: Temel Kural
Olay döngüsünün en önemli kuralı:
- Çağrı yığınındaki tüm eşzamanlı kodları çalıştır
- Mikro görev kuyruğunu tamamen temizleyin (Promise, MutationObserver)
- Çalıştır sonraki makro görev geri arama kuyruğundan (setTimeout, G/Ç geri arama)
- 2. noktaya geri dön
Bu, birçok geliştiriciyi şaşırtan davranışı açıklıyor:
console.log('1 - sincrono');
setTimeout(() => console.log('4 - macrotask'), 0);
Promise.resolve()
.then(() => console.log('2 - microtask 1'))
.then(() => console.log('3 - microtask 2'));
console.log('1b - sincrono');
// Output:
// 1 - sincrono
// 1b - sincrono
// 2 - microtask 1
// 3 - microtask 2
// 4 - macrotask
// Perché? Il setTimeout con delay=0 è comunque un macrotask.
// Le Promise sono microtask, eseguite PRIMA del prossimo macrotask.
Olay Döngüsünü Adım Adım Görüntüleyin
// Esempio completo — traccia mentale dell'esecuzione
async function main() {
console.log('A'); // [1] push main, push log('A')
setTimeout(() => console.log('B'), 0); // [2] delega a Web APIs
await Promise.resolve('resolved'); // [3] sospende main, schedula microtask
console.log('C'); // [6] riprende dopo microtask
}
console.log('D'); // [4] esegue sincrono
main(); // [1] chiama main
console.log('E'); // [5] esegue sincrono dopo main si sospende
// Esecuzione:
// [1] D (sincrono prima di main)
// [2] A (main inizia, prima console.log)
// [3] main si sospende su await
// [4] E (codice sincrono dopo main())
// [5] Stack vuoto, microtask queue: Promise.resolve callback
// [6] C (main riprende dopo await)
// [7] Stack vuoto, macrotask queue: setTimeout callback
// [8] B
// Output finale: D, A, E, C, B
Söz verme ve eşzamansız/bekleme: Kaputun Altında
async/await Vaatlerin üstünde sözdizimsel şeker var. Derleyici dönüştürür
her await bir .then(). Dönüşümü anlamak anlamaya yardımcı olur
infaz sırası:
// Questa funzione async:
async function processUser(id) {
const user = await fetchUser(id); // await punto 1
const orders = await fetchOrders(user); // await punto 2
return { user, orders };
}
// È equivalente a:
function processUserWithPromises(id) {
return fetchUser(id)
.then(user => {
return fetchOrders(user)
.then(orders => { return { user, orders }; });
});
}
// La funzione si "sospende" a ogni await e
// riprende quando la Promise risolve (tramite microtask queue)
Mikro Görev Açlığı: Kaçınılması Gereken Bir Anti-Desen
Mikro görev kuyruğu hiçbir zaman boşaltılmazsa makro görevler (ve dolayısıyla G/Ç geri aramaları) boşaltılmaz. asla gerçekleştirilmedi. Buna denir mikro görev açlığı:
// ANTI-PATTERN: loop infinito di microtask — blocca tutto!
function recursiveMicrotask() {
Promise.resolve().then(recursiveMicrotask); // schedula infiniti microtask
}
recursiveMicrotask();
// setTimeout di seguito non verrà MAI eseguito!
// PATTERN CORRETTO: usa setImmediate (Node.js) o setTimeout per cedere il controllo
function processLargeDataset(data, index = 0) {
if (index >= data.length) return;
processItem(data[index]);
// Cede il controllo all'event loop ogni 100 elementi
if (index % 100 === 0) {
setImmediate(() => processLargeDataset(data, index + 1));
} else {
processLargeDataset(data, index + 1);
}
}
Node.js Olay Döngüsü: Aşamalar
Node.js olay döngüsünün, tarayıcının ötesine geçen belirli aşamaları (libuv) vardır:
// Le fasi del Node.js event loop (semplificato)
// 1. timers: esegue callback di setTimeout e setInterval
// 2. I/O callbacks: callback I/O differite (errori socket)
// 3. idle, prepare: uso interno Node.js
// 4. poll: recupera I/O events, esegue callback I/O
// 5. check: esegue callback di setImmediate()
// 6. close callbacks: es. socket.on('close', callback)
// setImmediate vs setTimeout(fn, 0): NON garantiti nell'ordine
// dipende dal momento di chiamata nel ciclo
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// Output non deterministico se eseguiti nel main module
// Ma dentro una callback I/O:
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// Qui 'immediate' è sempre PRIMA — siamo già nella fase poll
});
Olay Döngüsünü Engellemek: Klasik Hatalar
Olay Döngüsünü Engelleyen İşlemler
- Eşzamanlı CPU yoğun bilgi işlem: 2 saniyelik blokları hesaplayan bir döngü diğer tüm istekler 2 saniye süreyle. CPU'ya bağlı işler için Worker Threads'i kullanın.
- Büyük yüklerde JSON.parse(): 10 MB'lık bir JSON'un ayrıştırılması senkron ve bloklar. JSON akışlarını kullanın veya Worker Thread'e yetki verin.
- fs.readFileSync() sunucu kodunda: Tüm Node.js API'lerinin sürümlerini senkronize edin ipliği bloke ediyorlar. Her zaman geri aramalı veya beklemeli eşzamansız sürümleri kullanın.
-
Bayraksız kripto işlemleri: Şifreleme işlemleri CPU'ya bağlıdır.
crypto.scrypt()geri aramaları kabul edin;crypto.scryptSync()engellemek.
Sonuçlar
Olay döngüsü, tek iş parçacıklı olmasına rağmen JavaScript'i verimli kılan şeydir.
Hatırlanması gereken anahtarlar: mikro görev kuyruğunun makro görev kuyruğuna göre önceliği vardır; await
iş parçacığını engellemez ancak geçerli işlevi askıya alır; CPU'ya bağlı senkronize işlem blokları
tüm olay döngüsü.
Bir sonraki yazımızda bunları inceleyeceğiz goroutinler ve Go kanalları: bir model CSP'yi temel alan ve milyonlarca göreve ölçeklenmek üzere tasarlanmış tamamen farklı eşzamanlılık platformu rakipler.







