- Çağrı yığınını, görev kuyruğunu ve mikro görev kuyruğunu tanımlamak
- Senkron kod,
setTimeout, Promise veawaitkarışık kodun çıktı sırasını tahmin etmek - Sıralı ve paralel asenkron kod arasında seçim yapmak:
Promise.all,allSettled,race,any
Mülakatların klasik sorusu: “Bu kod hangi sırayla ne yazdırır?” ve önünde bir console.log, setTimeout(..., 0) ve Promise.then karışımı. Deneyimli geliştiriciler bile sık sık yanılır. JavaScript tek bir iş parçacığında çalışır, ama binlerce tıklamayı, isteği ve zamanlayıcıyı yönetir. Sır olay döngüsündedir (event loop). Üç yapıyı, yani çağrı yığınını, görev kuyruğunu ve mikro görev kuyruğunu anladığında bu bulmacalar basit bir hesaba dönüşür.
Çağrı yığını
Çağrı yığını, şu anda çalışan fonksiyonların listesidir. Bir fonksiyon çağrıldığında yığının üstüne yeni bir “çerçeve” konur, fonksiyon döndüğünde kaldırılır. JavaScript'in tek bir yığını vardır, yani aynı anda yalnızca tek bir iş yapar. Yığın meşgulken başka hiçbir şey olamaz: tıklamalar işlenmez, zamanlayıcılar çalışmaz, sayfa yeniden çizilmez.
function third() {
console.log('3. third is on top of the stack');
}
function second() {
console.log('2. second calls third');
third();
console.log('4. back in second');
}
function first() {
console.log('1. first calls second');
second();
console.log('5. back in first');
}
first();▸ Beklenen çıktı
1. first calls second 2. second calls third 3. third is on top of the stack 4. back in second 5. back in first
first → second → third. Her fonksiyon ancak içindeki çağrı bittikten sonra devam eder. Sonsuz özyineleme yığını doldurur ve motor bir hata fırlatır.Görevler ve mikro görevler
Asenkron bir iş (zamanlayıcı, ağ yanıtı, tıklama) bittiğinde callback'i hemen çalışmaz; bir kuyrukta bekler. İki ana kuyruk vardır. Görev kuyruğuna (macrotask) setTimeout, setInterval, kullanıcı olayları ve mesajlar girer. Mikro görev kuyruğuna ise Promise callback'leri (then, catch, finally), await'ten sonraki kod ve queueMicrotask girer. Olay döngüsü bunları şöyle işler:
- 1Mevcut kodu sonuna kadar çalıştır
Betik (ya da bir görev) yığın boşalana kadar çalışır.
- 2TÜM mikro görevleri çalıştır
Bu sırada eklenen yeni mikro görevler dahil, kuyruk tamamen boşalana kadar.
- 3Gerekirse sayfayı çiz
Tarayıcı değişiklikleri ekrana yansıtır.
- 4Kuyruktan TEK bir görev al
Örneğin bir zamanlayıcı callback'i; ardından 1. adıma dön.
console.log('A: script start');
setTimeout(() => console.log('B: timeout'), 0);
Promise.resolve()
.then(() => console.log('C: promise 1'))
.then(() => console.log('D: promise 2'));
queueMicrotask(() => console.log('E: microtask'));
console.log('F: script end');▸ Beklenen çıktı
A: script start F: script end C: promise 1 E: microtask D: promise 2 B: timeout
Kuyrukta async/await
await yalnızca kendi async fonksiyonunu duraklatır; await'ten sonraki kod bir mikro görev olarak devam eder. Dikkat: ilk await'e kadar olan kod, fonksiyon çağrıldığı anda senkron olarak çalışır.
async function load() {
console.log('2. load starts: synchronous part');
await null;
console.log('4. after await: microtask');
}
console.log('1. script start');
setTimeout(() => console.log('6. timeout: task'), 0);
load();
Promise.resolve().then(() => console.log('5. then: microtask'));
console.log('3. script end');▸ Beklenen çıktı
1. script start 2. load starts: synchronous part 3. script end 4. after await: microtask 5. then: microtask 6. timeout: task
Mikro görev kuyruğu bir sonraki görevden önce tamamen boşaltılır. Bu yüzden bir zamanlayıcının içinde oluşturulan Promise, bir sonraki zamanlayıcıdan önce çalışır. Aynı nedenle kendini sonsuza dek kuyruğa ekleyen bir mikro görev, sayfayı sonsuz bir while döngüsü gibi dondurur.
setTimeout(() => {
console.log('timeout 1');
Promise.resolve().then(() => console.log('-> microtask inside timeout 1'));
}, 0);
setTimeout(() => console.log('timeout 2'), 0);
Promise.resolve().then(() => {
console.log('microtask 1');
setTimeout(() => console.log('timeout 3, added by microtask 1'), 0);
});▸ Beklenen çıktı
microtask 1 timeout 1 -> microtask inside timeout 1 timeout 2 timeout 3, added by microtask 1
Sıralı ve paralel
JavaScript tek iş parçacıklıdır, ama beklemeler üst üste binebilir: ağ isteklerini ve zamanlayıcıları arka planda tarayıcı (ya da Node.js) izler. Buna eşzamanlılık (concurrency) denir: aynı anda birçok iş “sürmektedir”, ama her an yalnızca bir JavaScript parçası çalışır. Sık yapılan hata: döngü içindeki await, istekleri birbiri ardına gönderir. İşler bağımsızsa hepsini birlikte başlat ve Promise.all ile bekle.
const task = (label, ms) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(`${label} done`);
resolve(label);
}, ms);
});
console.log('Sequential:');
await task('A (200 ms)', 200);
await task('B (100 ms)', 100);
console.log('Parallel:');
const results = await Promise.all([task('A (200 ms)', 200), task('B (100 ms)', 100)]);
console.log(results);▸ Beklenen çıktı
Sequential: A (200 ms) done B (100 ms) done Parallel: B (100 ms) done A (200 ms) done [ 'A (200 ms)', 'B (100 ms)' ]
Promise.all sonuçları dizideki sırayla döndürür. En üst düzeyde await burada, modüllerde olduğu gibi çalışır.| Metot | Ne zaman sonuçlanır | Sonuç |
|---|---|---|
Promise.all | hepsi başarılı olunca ya da ilk hatada | değerler dizisi ya da ilk hata |
Promise.allSettled | hepsi bitince; asla reddedilmez | { status, value } / { status, reason } dizisi |
Promise.race | ilki sonuçlanınca (başarı ya da hata) | en hızlı sonuç |
Promise.any | ilk başarıda; hepsi başarısızsa hata | ilk başarılı değer ya da AggregateError |
const ok = (value, ms) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
const fail = (reason, ms) =>
new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
const settled = await Promise.allSettled([ok('menu', 100), fail('no stock', 50)]);
console.log(settled.map((result) => result.status));
console.log(await Promise.any([fail('server 1 down', 50), ok('server 2', 100)]));
console.log(await Promise.race([ok('fast', 50), ok('slow', 150)]));
try {
await Promise.all([ok('menu', 100), fail('no stock', 50)]);
} catch (error) {
console.log('Promise.all failed:', error.message);
}▸ Beklenen çıktı
[ 'fulfilled', 'rejected' ] server 2 fast Promise.all failed: no stock
Hiçbir satırın yerini değiştirmeden çıktının 1, 2, 3, 4 sırasıyla gelmesini sağla: gereken satırları setTimeout(() => ..., 0) ya da Promise.resolve().then(() => ...) ile sar.
console.log('4. task');
console.log('3. microtask');
console.log('1. sync');
console.log('2. sync');▸ Beklenen çıktı
1. sync 2. sync 3. microtask 4. task
Fiyatlar sırayla yükleniyor ve bu yavaş. Kodu, üç isteğin aynı anda başlayacağı şekilde değiştir: Promise.all ve dizi destructuring'i kullan. Yükleme sırası değişecek, toplam ise aynı kalacak.
const prices = { tea: 2, bread: 1, jam: 5 };
function getPrice(item, ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`loaded ${item}`);
resolve(prices[item]);
}, ms);
});
}
// sequential and slow: rewrite with Promise.all
const tea = await getPrice('tea', 300);
const bread = await getPrice('bread', 100);
const jam = await getPrice('jam', 200);
console.log('Total:', tea + bread + jam);▸ Beklenen çıktı
loaded bread loaded jam loaded tea Total: 8
Önemli noktalar
- JavaScript'in tek bir çağrı yığını vardır: aynı anda yalnızca bir kod parçası çalışır, uzun senkron iş her şeyi dondurur.
- Zamanlayıcılar ve olaylar görev kuyruğuna, Promise callback'leri ve
await'ten sonraki kod mikro görev kuyruğuna girer. - Sıra: senkron kod → tüm mikro görevler → tek bir görev → yine tüm mikro görevler.
- Bir
asyncfonksiyonda ilkawait'e kadar olan kod senkron çalışır. - Bağımsız işleri birlikte başlat ve
Promise.all/allSettledile bekle;raceveanyilk sonucu alır.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); console.log(4); neyi hangi sırayla yazdırır?