---
title: 'Eşzamansız JavaScript ve Çalışma Zamanı'
source: 'https://academia.sh/tr/kurslar/eszamansiz-javascript'
language: tr
updated: '2026-08-17T04:41:26+00:00'
license: 'CC BY-SA 4.0'
lessons: 17
outcomes:
    - 'Olay döngüsünün görev ve mikro görev kuyruklarını sırayla açıklar'
    - 'Söz (promise) tabanlı akışları hata yönetimiyle birlikte kurar'
    - 'Bellek sızıntısı ve başarım sorunlarını araçlarla tanılar'
---

# Eşzamansız JavaScript ve Çalışma Zamanı

Tek iş parçacıklı yürütme modeli, olay döngüsünün görev ve mikro görev kuyrukları, sözlerin durum makinesi, iptal ve zaman aşımı, bellek yaşam döngüsü ve başarım tanısı.

## Yürütme Modeli

Tek iş parçacıklı yürütmenin sonuçları, olay döngüsünün çağrı yığıtı ile görev ve mikro görev kuyrukları, zamanlayıcıların gerçek anlamı ve geri çağrı tabanlı eşzamansızlık.

### Tek İş Parçacıklı Model

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/tek-is-parcacikli-model

JavaScript'in tek iş parçacıklı, sonuna kadar çalışan yürütme modeli; engelleyen işin sonuçları; dilin çekirdeği ile çalışma zamanının sağladığı eşzamansız yeteneklerin ayrımı.

Önceki iki kurs dilin değer modelini kurdu: değerler, nesneler, prototip zinciri ve
kapanışlar. Bunların tümü tek bir varsayım altında anlatıldı — kod yazıldığı sırada
çalışır, bir deyim biter, sonraki başlar. Bu kurs o varsayımı kaldırır.

Bir ölçüm istasyonundan veri istediğinizde yanıt hemen gelmez. Yanıt beklenirken program
ne yapar? "Bekler" yanıtı, tek iş parçacıklı bir dilde çok pahalıya mal olur. Bu ders,
bekleme kararının neden verilemeyeceğini ve dilin bunun yerine ne yaptığını kurar.

#### Tek İş Parçacığı Ne Anlama Gelir

Bir **iş parçacığı (thread)**, komutları sırayla yürüten tek bir yürütme akışıdır. Bir
JavaScript programında bu akıştan yalnız bir tane vardır: herhangi bir anda kullanıcı
kodunun tek bir satırı çalışır. İki fonksiyon aynı anda ilerlemez, iki döngü iç içe
geçmez, iki geri çağrı çakışmaz.

Bunun doğrudan bir sonucu vardır: **veri yarışı (data race) yoktur.** Bir değişkeni
okuyup yazdığınız iki satır arasına başka bir kod giremez. Diğer dillerde kilit
gerektiren bir sayaç artırımı, burada gerekmez.

İşletim sisteminin süreç ve iş parçacığı kavramları, çekirdek düzeyindeki zamanlama ve
gerçek paralellik ayrı bir konudur; İşletim Sistemi Kavramları kursunda ele alınır. Bu
kurs, tek bir iş parçacığına düşen kod üzerinde durur.

#### Sonuna Kadar Çalışma

Modelin ikinci kuralı, tek iş parçacığı kadar bağlayıcıdır: bir kez çalışmaya başlayan
bir iş, kendisi bitene kadar bölünmez. Buna **sonuna kadar çalışma (run-to-completion)**
denir.

Bunun anlamı, "sıfır milisaniye sonra çalıştır" diye kaydedilmiş bir işin bile önündeki
işin bitmesini beklemesidir.

```js
function toplamHesapla(tur) {
  let toplam = 0;
  for (let i = 0; i < tur; i += 1) toplam += i % 7;
  return toplam;
}

setTimeout(() => console.log("3 — zamanlayıcı geri çağrısı"), 0);

console.log("1 — hesap başlıyor");
const sonuc = toplamHesapla(5_000_000);
console.log("2 — hesap bitti, sonuç:", sonuc);
```

```
1 — hesap başlıyor
2 — hesap bitti, sonuç: 14999995
3 — zamanlayıcı geri çağrısı
```

Geri çağrı en başta kaydedildi ve gecikmesi sıfırdı; buna karşın en sonda çalıştı.
`setTimeout` bir işi *şimdi* çalıştırmaz, *sıraya* koyar. Sıradaki iş ancak yürüyen kod
bittikten sonra ele alınır. Bu ilkenin ayrıntısı sonraki derste açılacak.

Sonuna kadar çalışma, tek iş parçacığının doğal eşlikçisidir. Yürüyen bir fonksiyonun
ortasında başka bir geri çağrının çalışabildiğini varsayarsanız, kilitlere ve yeniden
girilebilirlik (reentrancy) tartışmasına geri dönersiniz. Model, bu karmaşıklığı
program yazarından uzak tutmak için basitliği seçer — bedeli, sıradaki her işin
öndekini beklemesidir.

#### Engelleyen İş

**Engelleyen (blocking)** iş, iş parçacığını başka hiçbir şey yapamayacak biçimde meşgul
eden iştir. İki kaynağı vardır.

Birincisi uzun süren hesaplamadır: milyonlarca elemanlı bir dizinin sıralanması, büyük
bir metnin ayrıştırılması. Bunlar işi gerçekten yapar, yalnız uzun sürer.

İkincisi ve daha sinsi olanı, **eşzamanlı (synchronous) girdi-çıktı** çağrılarıdır. Bir
dosyayı eşzamanlı okuyan bir çağrı, disk yanıt verene kadar iş parçacığını tutar; oysa bu
sürede iş parçacığı hiçbir hesap yapmaz, yalnız bekler.

```js
import { writeFileSync, readFileSync, readFile } from "node:fs";

writeFileSync("olcumler.txt", "A1;21.4\nB2;19.8\nC3;23.1\n");

readFile("olcumler.txt", "utf8", (hata, veri) => {
  console.log("3 — eşzamansız okuma bitti:", veri.trim().split("\n").length, "satır");
});

const icerik = readFileSync("olcumler.txt", "utf8");
console.log("1 — eşzamanlı okuma bitti:", icerik.trim().split("\n").length, "satır");
console.log("2 — betiğin geri kalanı");
```

```
1 — eşzamanlı okuma bitti: 3 satır
2 — betiğin geri kalanı
3 — eşzamansız okuma bitti: 3 satır
```

İki okuma da aynı dosyayı okur ve aynı sonucu üretir. Ayrıldıkları nokta, bekleme
sırasında iş parçacığının kime ait olduğudur. Eşzamanlı olan onu tutar; eşzamansız olan
bırakır ve sonucu hazır olduğunda geri çağrıyla haber verir.

Engellemenin görünen sonucu ortama göre değişir. Tarayıcıda arayüz donar: tıklama işlenmez,
çizim güncellenmez, kaydırma takılır. Sunucuda ise tek bir isteğin uzun hesabı, o sırada
gelen bütün istekleri kuyrukta bekletir. Her iki durumda da nedeni aynıdır: iş parçacığı
tek, sıradaki iş de sabırlı.

#### Dil Nerede Biter, Çalışma Zamanı Nerede Başlar

Dilin çekirdeği eşzamansızlığı tanımlamaz. Standart, sözleri (promise) ve `async`
fonksiyonları tanımlar; ancak "on milisaniye sonra" veya "dosya okunduğunda" gibi
kavramların hiçbiri dilin sözdiziminde yer almaz.

Bu yetenekleri **ana ortam (host environment)** sağlar. JavaScript Temelleri kursunda
tanıtılan ayrım burada işlevsel hale gelir:

| Katman | Sağladığı şey |
|---|---|
| Dil çekirdeği | Değerler, fonksiyonlar, sözler, `async`/`await`, mikro görev kuralı |
| Ana ortam | Zamanlayıcılar, ağ istekleri, dosya erişimi, olaylar, görev kuyruğu, olay döngüsü |

`setTimeout` bir dil sözcüğü değildir; ana ortamın kullanıma açtığı bir fonksiyondur. Aynı
biçimde ağ isteği başlatan fonksiyon da ana ortama aittir. Bu nedenle aynı JavaScript kodu
farklı ortamlarda farklı yeteneklerle karşılaşır: tarayıcıda belge nesnesi vardır, sunucu
çalışma zamanında dosya sistemi vardır.

Ayrım, sorumluluğu da böler. Beklemeyi *dil* yapmaz. Zamanlayıcıyı ana ortam kurar, süre
dolduğunda geri çağrıyı kuyruğa koyar; iş parçacığı boşaldığında JavaScript o geri çağrıyı
çalıştırır. Bekleme sırasında iş parçacığı serbesttir.

#### Eşzamansızlık Paralellik Değildir

Bu ayrım kursun tamamında geçerlidir ve baştan yerleşmelidir.

**Paralellik**, iki işin aynı anda yürütülmesidir; birden çok iş parçacığı ister.
**Eşzamansızlık**, bir işin başlatılıp sonucunun sonra alınmasıdır; tek iş parçacığıyla da
mümkündür.

Üç ölçüm isteğini "aynı anda" başlattığınızda, bu isteklerin ağ üzerindeki bekleme
süreleri gerçekten üst üste biner — çünkü beklemeyi yapan ana ortamdır, JavaScript değil.
Ama yanıtları işleyen kodunuz sırayla çalışır: birinci yanıt işlenirken ikincisi kuyrukta
bekler. Böylece programınız beklemede paralel, hesapta sıralıdır.

Bunun pratik sonucu şudur: eşzamansızlık, girdi-çıktı ağırlıklı işi hızlandırır; hesap
ağırlıklı işi hızlandırmaz. Milyon elemanlık bir sıralamayı `setTimeout` içine koymak onu
hızlandırmaz, yalnız başlangıcını erteler. Hesap yükünü gerçekten dağıtmak için ayrı bir
iş parçacığı gerekir; çalışma zamanları bunu ayrı bir yürütme bağlamı olarak sunar ve
bağlamlar arasında varsayılan olarak yalnız ileti geçişi yapılır — sıradan değişkenler
paylaşılmaz. Tek istisna açıkça kurulan paylaşımlı bellektir (`SharedArrayBuffer`); orada
veri yarışı yeniden doğar ve eşzamanlama gerekir.

#### Kursun Örneği: Ölçüm Akışı

Kurs boyunca tek bir somut örnek üzerinde ilerlenecek: birkaç ölçüm istasyonundan
sıcaklık değeri toplayan küçük bir program. İstasyonların yanıt gecikmeleri farklıdır;
bazıları hata döndürür, bazıları hiç yanıt vermez.

Bu örnek ders ders şu adımlardan geçecek: geri çağrıyla tek ölçüm, sözle zincirlenmiş
ölçüm, birleştiricilerle toplu ölçüm, zaman aşımıyla sınırlanmış ölçüm, iptal edilebilir
ölçüm ve son olarak sızıntı bırakmayan sürekli ölçüm akışı.

Program boyunca yazdırılan hiçbir şey süre olmayacak; yalnız **sıra** olacak. Süre
makineye ve yüke göre değişir, sıra ise modelin kuralıdır.

#### Özet

- JavaScript kullanıcı kodu tek bir iş parçacığında yürür; iki kod parçası aynı anda
  ilerlemez, bu nedenle veri yarışı oluşmaz.
- Sonuna kadar çalışma kuralı gereği, başlayan bir iş bitmeden sıradaki iş ele alınmaz;
  sıfır gecikmeli bir geri çağrı bile bunu bekler.
- Engelleyen iş, iş parçacığını meşgul ederek bekleyen bütün işleri geciktirir; uzun
  hesap ve eşzamanlı girdi-çıktı iki kaynağıdır.
- Zamanlayıcı, ağ ve dosya erişimi dilin değil ana ortamın yetenekleridir; dil yalnız
  sözleri ve mikro görev kuralını tanımlar.
- Eşzamansızlık beklemeleri üst üste bindirir, hesabı paralelleştirmez.

#### Sonraki Adım

Bu derste geri çağrıların "sıraya girdiği" söylendi ama sıranın nasıl işlediği
söylenmedi. Sıradaki her işin eşit olmadığı, bir söz geri çağrısının bir zamanlayıcı geri
çağrısından önce çalıştığı da gösterilmedi. Sonraki ders bu düzeneği açar: çağrı yığıtı,
görev kuyruğu ve mikro görev kuyruğu ayrımıyla olay döngüsünün tam kuralını kurar ve
karışık bir örnekte çıktı sırasını satır satır gerekçelendirir.

### Olay Döngüsü

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/olay-dongusu

Çağrı yığıtı, görev kuyruğu ve mikro görev kuyruğunun ayrımı; olay döngüsünün tur kuralı; karışık bir örnekte çıktı sırasının satır satır gerekçelendirilmesi.

Önceki ders geri çağrıların "sıraya girdiğini" söyledi. Sıra tek değildir ve içindeki
işler eşit öncelikli değildir. Bir söz geri çağrısı, kendisinden önce kaydedilmiş bir
zamanlayıcı geri çağrısından önce çalışır; bu, öncelik değil, kuyruk farkıdır.

Bu ders, o kuyrukları ve aralarındaki kuralı kurar. Kural öğrenildiğinde, karışık bir
programın çıktı sırası çalıştırmadan önce söylenebilir hale gelir — bu kursun geri kalanı
boyunca kullanılacak temel yetkinlik budur.

#### Çağrı Yığıtı

**Çağrı yığıtı (call stack)**, o anda yürüyen fonksiyon çağrılarını tutan yapıdır.
Programlama Temelleri kursunda tanıtılan çağrı çerçevesi kavramı burada aynen geçerlidir:
her çağrı bir çerçeve iter, her `return` bir çerçeve çıkarır.

Yığıt boşaldığında, o anda yürüyen bir iş kalmamış demektir. Önceki dersteki sonuna kadar
çalışma kuralının kesin ifadesi budur: **bir sonraki iş, ancak yığıt boşaldığında
başlar.**

Bunun gözlenebilir bir sonucu vardır. Bir `try` bloğu, yalnız kendi yığıt çerçevesi
yaşarken oluşan hataları yakalar. Geri çağrı ise boş yığıtta, bambaşka bir turda çalışır:

```js
process.on("uncaughtException", (hata) => {
  console.log("2 — çalışma zamanı düzeyinde yakalandı:", hata.message);
});

try {
  setTimeout(() => {
    throw new Error("geri çağrının içinden");
  }, 0);
  console.log("1 — try bloğu sorunsuz bitti");
} catch (hata) {
  console.log("bu satır hiç çalışmaz:", hata.message);
}
```

```
1 — try bloğu sorunsuz bitti
2 — çalışma zamanı düzeyinde yakalandı: geri çağrının içinden
```

`try` bloğu yalnız `setTimeout` çağrısının kendisini kapsar; geri çağrının gövdesini
kapsamaz. Geri çağrı çalıştığında `try` bloğunun çerçevesi çoktan yığıttan çıkmıştır. Bu
gözlem, kursun ilerleyen bölümlerinde eşzamansız hata yönetiminin neden ayrı bir düzenek
gerektirdiğini açıklayacak.

#### Görev Kuyruğu ve Olay Döngüsü

Ana ortam, tamamlanan işleri **görev kuyruğuna (task queue)** koyar: süresi dolan bir
zamanlayıcı, gelen bir ağ yanıtı, tıklanan bir düğme. Kuyruktaki her giriş bir
**görev (task)** — bazı kaynaklarda makro görev — yani baştan sona çalıştırılacak bir
geri çağrıdır.

**Olay döngüsü (event loop)**, iki adımı sonsuza kadar tekrarlayan denetim akışıdır:

1. Yığıt boşsa, görev kuyruğundan bir görev al ve sonuna kadar çalıştır.
2. Görev bittikten sonra, mikro görev kuyruğunu tamamen boşalt.

Bu iki adıma **tur** denir. Tarayıcı ortamında turların arasına çizim (render) adımı da
girer; bu nedenle uzun bir görev, çizimi de erteler.

#### Mikro Görev Kuyruğu

**Mikro görev (microtask)**, aynı turun sonunda çalışması güvence altına alınmış küçük
iştir. Söz geri çağrıları ve `queueMicrotask` ile kaydedilen işler bu kuyruğa girer.

İki kuyruk arasındaki fark tek cümleyle ifade edilir: **görev kuyruğundan tur başına bir
iş alınır; mikro görev kuyruğu ise tamamen boşaltılır.**

Boşaltma sırasında eklenen yeni mikro görevler de aynı boşaltmaya dahildir. Yani bir mikro
görev, başka bir mikro görev üretirse, o da sıradaki göreve geçilmeden önce çalışır.

```js
setTimeout(() => {
  console.log("A — birinci görev");
  queueMicrotask(() => console.log("B — birinci görevin mikro görevi"));
  setTimeout(() => console.log("D — birinci görevin ürettiği görev"), 0);
}, 0);

setTimeout(() => console.log("C — ikinci görev"), 0);
```

```
A — birinci görev
B — birinci görevin mikro görevi
C — ikinci görev
D — birinci görevin ürettiği görev
```

Birinci görev, hem bir mikro görev hem bir görev üretti. Mikro görev, ikinci göreve
geçilmeden önce çalıştı; üretilen görev ise kuyruğun sonuna eklendiği için en sona kaldı.

#### Karışık Bir Örnekte Sıra

Kural, eşzamanlı kod ile iki kuyruğun birlikte göründüğü bir örnekte sınanır. Aşağıdaki
programın çıktı sırasını, çalıştırmadan önce söyleyebilmelisiniz.

```js
console.log("1 — eşzamanlı");
setTimeout(() => console.log("6 — görev kuyruğu (setTimeout)"), 0);
Promise.resolve().then(() => console.log("4 — mikro görev (söz)"));
queueMicrotask(() => console.log("5 — mikro görev (queueMicrotask)"));
console.log("2 — eşzamanlı");
(function () {
  console.log("3 — eşzamanlı (iç fonksiyon)");
})();
```

```
1 — eşzamanlı
2 — eşzamanlı
3 — eşzamanlı (iç fonksiyon)
4 — mikro görev (söz)
5 — mikro görev (queueMicrotask)
6 — görev kuyruğu (setTimeout)
```

Gerekçe satır satır şudur:

- **1, 2, 3** — Bunlar yürüyen işin kendisidir. Yığıt boşalana kadar hiçbir kuyruğa
  bakılmaz. Ara satırlardaki `setTimeout`, `then` ve `queueMicrotask` çağrıları yalnız
  *kayıt* yapar; kaydettikleri fonksiyonu çalıştırmaz.
- **4, 5** — Yığıt boşaldı. Mikro görev kuyruğu tamamen boşaltılır. İçindeki iki iş,
  kaydedilme sırasına göre çalışır: önce söz geri çağrısı, sonra `queueMicrotask`.
- **6** — Mikro görev kuyruğu boş. Ancak şimdi görev kuyruğundan bir görev alınır.

Sıranın kaynağı önceliğin kendisi değil, kuyruk yapısıdır: `setTimeout` en başta
kaydedildiği halde en sona kaldı; çünkü farklı bir kuyruğa girdi.

#### Mikro Görev Kuyruğu Aç Bırakabilir

"Tamamen boşaltılır" kuralının bir bedeli vardır: kendini yeniden kaydeden bir mikro görev
zinciri, görev kuyruğunu süresiz bekletir.

```js
let sayac = 0;

setTimeout(() => console.log("zamanlayıcı çalıştı; biriken mikro görev:", sayac), 0);

function zincir() {
  sayac += 1;
  if (sayac < 100000) queueMicrotask(zincir);
}
queueMicrotask(zincir);
```

```
zamanlayıcı çalıştı; biriken mikro görev: 100000
```

Yüz bin mikro görevin tamamı, sıfır gecikmeli zamanlayıcıdan önce çalıştı. Sayaç sınırı
kaldırılsaydı zamanlayıcı hiç çalışmayacaktı. Buna **açlık (starvation)** denir: bir
kuyruk, diğerinin çalışma fırsatını tüketir.

Pratik sonucu şudur: uzun ve bölünebilir bir işi mikro görevlere bölmek çözüm değildir.
Ana ortama gerçekten sıra vermek için işi görev kuyruğuna aktarmak gerekir. Bu kalıp,
başarım konusunda ayrıntılandırılacak.

#### Kuralı Ölçüm Akışına Uygulamak

Kural, kursun örneğinde de aynen işler. Aşağıdaki program iki istasyondan ölçüm ister;
istasyonların yanıt gecikmeleri farklıdır ve yanıt geldiğinde bir kayıt işi mikro göreve
bırakılır.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
};

function olcumIste(istasyon, gericagri) {
  console.log("istek kaydedildi:", istasyon);
  const kayit = KAYNAK[istasyon];
  setTimeout(() => gericagri({ istasyon, deger: kayit.deger }), kayit.gecikme);
}

olcumIste("A1", (olcum) => {
  console.log("görev: yanıt işleniyor —", olcum.istasyon);
  queueMicrotask(() => console.log("mikro görev: kayıt tamamlandı —", olcum.istasyon));
});

olcumIste("B2", (olcum) => {
  console.log("görev: yanıt işleniyor —", olcum.istasyon);
  queueMicrotask(() => console.log("mikro görev: kayıt tamamlandı —", olcum.istasyon));
});

console.log("betiğin eşzamanlı kısmı bitti");
```

```
istek kaydedildi: A1
istek kaydedildi: B2
betiğin eşzamanlı kısmı bitti
görev: yanıt işleniyor — B2
mikro görev: kayıt tamamlandı — B2
görev: yanıt işleniyor — A1
mikro görev: kayıt tamamlandı — A1
```

İki gözlem önemlidir. Birincisi, istek sırası ile yanıt sırası aynı değildir: A1 önce
istendi, B2 önce yanıtladı. Eşzamansız bir akışta sonuçların geliş sırası, isteklerin
başlatılma sırasına bağlı değildir. İkincisi, her yanıtın kayıt mikro görevi kendi turunun
sonunda, bir sonraki yanıt işlenmeden çalışır; iki istasyonun işleme adımları birbirine
karışmaz.

#### Kuyruk Ayrıntıları Ortama Bağlıdır

İki kuyruklu model standarttır ve her ortamda geçerlidir. Buna karşılık çalışma zamanları
görev kuyruğunu evrelere bölebilir ve kendi ek kuyruklarını ekleyebilir. Bu ek kuyrukların
sözlerin mikro görev kuyruğuna göre yeri, aynı çalışma zamanında bile bağlama göre
değişebilir.

Aşağıdaki iki dosya aynı üç satırı içerir; yalnız modül biçimleri farklıdır. Modül
biçimlerinin kendisi Modüller, Araçlar ve Ekosistem kursunun konusudur.

```js
// sira.cjs
Promise.resolve().then(() => console.log("söz mikro görevi"));
process.nextTick(() => console.log("çalışma zamanına özgü kuyruk"));
console.log("eşzamanlı");
```

```
$ node sira.cjs
eşzamanlı
çalışma zamanına özgü kuyruk
söz mikro görevi
```

```js
// sira.mjs
Promise.resolve().then(() => console.log("söz mikro görevi"));
process.nextTick(() => console.log("çalışma zamanına özgü kuyruk"));
console.log("eşzamanlı");
```

```
$ node sira.mjs
eşzamanlı
söz mikro görevi
çalışma zamanına özgü kuyruk
```

İki kuyruğun göreli sırası ters döndü. Buradan çıkarılacak kural, hangi biçimin doğru
olduğu değildir: **taşınabilir kod, standart dışı kuyrukların göreli sırasına
dayanmamalıdır.** Eşzamanlı kod, mikro görevler ve görevler arasındaki üç aşamalı sıra
her ortamda güvenilirdir; ötesi ortamın ayrıntısıdır.

#### Özet

- Çağrı yığıtı yürüyen çağrıları tutar; bir sonraki iş ancak yığıt boşaldığında başlar.
- Olay döngüsü her turda görev kuyruğundan bir görev alır, ardından mikro görev kuyruğunu
  tamamen boşaltır.
- Mikro görevler aynı turda, görevler sonraki turlarda çalışır; bu nedenle sonradan
  kaydedilen bir söz geri çağrısı, önceden kaydedilen bir zamanlayıcıdan önce çalışır.
- Boşaltma sırasında üretilen mikro görevler de aynı boşaltmaya dahildir; bu, görev
  kuyruğunu aç bırakabilir.
- Üç aşamalı sıra (eşzamanlı kod, mikro görevler, görevler) standarttır; çalışma zamanına
  özgü ek kuyrukların yeri taşınabilir değildir.

#### Sonraki Adım

Kuyruk kuralı kurulduğuna göre, kuyruğa iş koyan en yaygın araca dönülebilir:
zamanlayıcılar. Sonraki ders, `setTimeout` içindeki sayının neden bir söz değil bir alt
sınır olduğunu, eşit gecikmeli zamanlayıcıların hangi sıraya girdiğini ve yinelemeli
çalıştırmanın kendi geri çağrısı uzun sürdüğünde nasıl kaydığını gösterir. Ölçüm akışının
ilk somut parçası — zamanlayıcıya dayalı sahte bir istasyon — orada kurulacak.

### Zamanlayıcılar

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/zamanlayicilar

Gecikmenin alt sınır oluşu, eşit gecikmeli zamanlayıcıların sırası, yinelemeli çalıştırmada üst üste binme, kendi kendini zamanlayan döngü ve iptal.

Önceki ders, kuyruğa iş koyan çağrıların yalnız kayıt yaptığını gösterdi. En sık
kullanılan kayıt aracı zamanlayıcılardır ve verilen sayı çoğu zaman yanlış okunur:
`setTimeout(f, 10)` çağrısı, `f`'nin on milisaniye sonra çalışacağını söylemez.

Bu ders o sayının ne söylediğini, eşit gecikmeli zamanlayıcıların hangi sıraya girdiğini
ve yinelemeli çalıştırmanın nerede kaydığını kurar. Ölçüm akışının ilk çalışan parçası da
burada, zamanlayıcıya dayalı sahte bir istasyon olarak ortaya çıkacak.

#### Gecikme Bir Alt Sınırdır

`setTimeout(geriCagri, gecikme)` çağrısının anlamı şudur: *geçen süre `gecikme` değerine
ulaşmadan geri çağrı kuyruğa alınmaz.* Kuyruğa alındıktan sonra ne zaman çalışacağı,
önündeki işlerin ne kadar süreceğine bağlıdır.

Aynı anda kaydedilen zamanlayıcılar, gecikmelerine göre sıraya girer; gecikmeleri eşitse
kaydedilme sıralarını korurlar.

```js
setTimeout(() => console.log("4 — 20 ms"), 20);
setTimeout(() => console.log("3 — 10 ms"), 10);
setTimeout(() => console.log("1 — 0 ms, ilk kayıt"), 0);
setTimeout(() => console.log("2 — 0 ms, ikinci kayıt"), 0);
```

```
1 — 0 ms, ilk kayıt
2 — 0 ms, ikinci kayıt
3 — 10 ms
4 — 20 ms
```

Sıfır gecikme "hemen" anlamına gelmez, "en erken fırsatta" anlamına gelir. Ana ortamlar
ayrıca bir taban gecikme uygular: HTML standardı, beşten derin iç içe geçmiş
zamanlayıcılarda alt sınırı dört milisaniyeye çıkarır; sunucu çalışma zamanlarında da
sıfır gecikme genellikle bir milisaniyeye yuvarlanır. Sayı ne olursa olsun, alt sınırdan
öteye söz verilmez.

#### Engelleme Alt Sınırı Aşar

Alt sınırın üst sınırı yoktur. Önceki turdaki iş uzun sürerse, süresi çoktan dolmuş bir
zamanlayıcı kuyrukta bekler.

```js
function meshgulBekle(sure) {
  const bitis = Date.now() + sure;
  while (Date.now() < bitis) {
    // İş parçacığını kasten meşgul tutar.
  }
}

setTimeout(() => {
  console.log("1 — 10 ms zamanlayıcısı; 50 ms boyunca iş parçacığını tutuyor");
  meshgulBekle(50);
  console.log("2 — 10 ms zamanlayıcısı bitti");
}, 10);

setTimeout(() => console.log("3 — 20 ms zamanlayıcısı"), 20);
```

```
1 — 10 ms zamanlayıcısı; 50 ms boyunca iş parçacığını tutuyor
2 — 10 ms zamanlayıcısı bitti
3 — 20 ms zamanlayıcısı
```

İkinci zamanlayıcının süresi, birinci geri çağrı hâlâ çalışırken doldu. Sonuna kadar
çalışma kuralı gereği araya giremedi; birinci iş bitene kadar bekledi. Bu, ölçülen
gecikmenin neden istenen gecikmeden büyük olabildiğinin tek nedenidir: iş parçacığı meşgul.

#### Yinelemeli Çalıştırma ve Üst Üste Binme

`setInterval(geriCagri, aralik)`, geri çağrıyı belirli aralıklarla kuyruğa koyar. Aralık,
*geri çağrının bitişinden sonraki* değil, *kaydedilme anlarındaki* sürelerle ölçülür. Geri
çağrı aralıktan uzun sürerse, ardışık çalıştırmalar arasında boşluk kalmaz.

Aşağıdaki iki program yalnız tek satırla ayrılır. Birincisinde geri çağrı kısa:

```js
let tik = 0;
const kimlik = setInterval(() => {
  tik += 1;
  console.log("aralık tiki", tik);
  if (tik === 3) clearInterval(kimlik);
}, 5);

setTimeout(() => console.log("30 ms zamanlayıcısı"), 30);
```

```
aralık tiki 1
aralık tiki 2
aralık tiki 3
30 ms zamanlayıcısı
```

Üç tik beşer milisaniyelik aralıklarla tamamlandı ve otuzuncu milisaniyedeki zamanlayıcı
sıraya girdi. İkincisinde ise her tik yirmi milisaniye iş yapıyor:

```js
function meshgulBekle(sure) {
  const bitis = Date.now() + sure;
  while (Date.now() < bitis) {
    // İş parçacığını kasten meşgul tutar.
  }
}

let tik = 0;
const kimlik = setInterval(() => {
  tik += 1;
  console.log("aralık tiki", tik);
  meshgulBekle(20);
  if (tik === 3) clearInterval(kimlik);
}, 5);

setTimeout(() => console.log("30 ms zamanlayıcısı"), 30);
```

```
aralık tiki 1
aralık tiki 2
30 ms zamanlayıcısı
aralık tiki 3
```

Sıra değişti. İkinci tik, otuzuncu milisaniye gelmeden başladı ve yirmi milisaniye
boyunca iş parçacığını tuttu; zamanlayıcı ancak o iş bittikten sonra çalışabildi. Üçüncü
tik ise arkasından, neredeyse ara vermeden geldi.

Bu davranışın iki sonucu vardır. Birincisi, `setInterval` ile kurulan bir akışta geri
çağrılar arasında dinlenme süresi güvence altında değildir. İkincisi, geri çağrı içinde
eşzamansız iş başlatılıyorsa, önceki çalıştırma bitmeden yenisi başlayabilir ve iki iş
üst üste biner.

#### Kendi Kendini Zamanlayan Döngü

Her çalıştırma arasında en az bir aralık boşluk isteniyorsa, yineleme `setTimeout` ile
kurulur: geri çağrı işini bitirdikten sonra bir sonrakini kendisi kaydeder.

Bu kalıp, ölçüm akışının ilk çalışan biçimidir. Sahte istasyonlar, gecikmeleri ve
değerleri sabit bir tablodan okunur; böylece program her çalıştırmada aynı çıktıyı verir.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function akisBasla(istasyonlar, aralik, gericagri) {
  let sayac = 0;
  let kimlik = null;
  let durduruldu = false;

  function tur() {
    const istasyon = istasyonlar[sayac % istasyonlar.length];
    sayac += 1;
    gericagri({ sira: sayac, istasyon, deger: KAYNAK[istasyon].deger });
    if (!durduruldu) kimlik = setTimeout(tur, aralik);
  }

  kimlik = setTimeout(tur, aralik);

  return function durdur() {
    durduruldu = true;
    clearTimeout(kimlik);
  };
}

const durdur = akisBasla(["A1", "B2", "C3"], 5, (olcum) => {
  console.log(olcum.sira, olcum.istasyon, olcum.deger);
  if (olcum.sira === 4) durdur();
});
```

```
1 A1 21.4
2 B2 19.8
3 C3 23.1
4 A1 21.4
```

`durduruldu` bayrağı gereksiz görünebilir; değildir. `durdur` işlevi, geri çağrının
*içinden* çağrılır ve o sırada bir sonraki tur henüz kaydedilmemiştir. Yalnız
`clearTimeout` çağrılsaydı, çağrı zaten çalışmış bir zamanlayıcıyı iptal ederdi, hemen
ardından `tur` yeni bir zamanlayıcı kaydederdi ve akış hiç durmazdı. Kendi kendini
zamanlayan döngülerde iptal, bir durum bayrağıyla birlikte kurulmalıdır.

#### Bekleyen Zamanlayıcı Programı Ayakta Tutar

Bekleyen bir zamanlayıcı, ana ortam için "yapılacak iş var" demektir. Sunucu çalışma
zamanı, bekleyen iş kalmayana kadar kapanmaz.

```js
process.on("exit", () => console.log("3 — çalışma zamanı kapanıyor"));

setTimeout(() => console.log("2 — bekleyen zamanlayıcı çalıştı"), 50);
console.log("1 — betiğin eşzamanlı kısmı bitti");
```

```
1 — betiğin eşzamanlı kısmı bitti
2 — bekleyen zamanlayıcı çalıştı
3 — çalışma zamanı kapanıyor
```

Aynı program `clearTimeout` ile iptal edilirse, kapanış hemen gerçekleşir:

```js
process.on("exit", () => console.log("2 — çalışma zamanı kapanıyor"));

const kimlik = setTimeout(() => console.log("bu satır hiç çalışmaz"), 50);
clearTimeout(kimlik);
console.log("1 — betiğin eşzamanlı kısmı bitti");
```

```
1 — betiğin eşzamanlı kısmı bitti
2 — çalışma zamanı kapanıyor
```

Bu gözlem ileride iki yerde işe yarayacak: iptal edilmemiş bir zamanlayıcı hem programın
kapanmasını geciktirir hem de geri çağrısının kapattığı bütün değerleri bellekte tutar.
İkincisi bellek konusunda ele alınacak sızıntı türlerinden biridir.

#### Zamanlayıcı Ne Değildir

Üç yaygın yanlış okuma vardır ve üçü de aynı kaynaktan çıkar.

`setTimeout(f, 0)` "hemen çalıştır" değildir; "yığıt boşalınca çalıştır" demektir. Bu
nedenle uzun bir işi ikiye bölmek için kullanılabilir, ama işi hızlandırmaz.

Zamanlayıcı bir kronometre değildir. Geri çağrı içinde geçen süreyi ölçmek için
zamanlayıcı gecikmesine güvenilemez; ölçüm ayrı bir araçla yapılır.

Zamanlayıcı bir eşzamanlılık aracı da değildir. İki geri çağrı asla aynı anda çalışmaz;
biri bitmeden diğeri başlamaz. Beklemeler üst üste biner, hesaplar binmez.

#### Özet

- Zamanlayıcı gecikmesi bir alt sınırdır; üst sınır yoktur ve meşgul iş parçacığı bu
  sınırı serbestçe aşar.
- Eşit gecikmeli zamanlayıcılar kaydedilme sıralarını korur.
- `setInterval` çalıştırmalar arasında boşluk güvencesi vermez; uzun süren geri çağrılarda
  tikler üst üste biner.
- Kendi kendini zamanlayan `setTimeout` döngüsü her tur arasında en az bir aralık bırakır;
  iptali için durum bayrağı gerekir.
- Bekleyen bir zamanlayıcı çalışma zamanını ayakta tutar ve geri çağrısının kapattığı
  değerleri bellekte tutar.

#### Sonraki Adım

Zamanlayıcılar geri çağrıyı zamana bağladı; sıradaki adım geri çağrıyı *sonuca* bağlamak.
Sonraki ders, ölçüm kaynağını hata bildirebilen bir geri çağrı arayüzüne dönüştürür; üç
istasyonu sırayla ve birlikte okumayı dener; ardından bu yaklaşımın sınırlarını gösterir:
iç içe geçme, hata yollarının tekrarı ve geri çağrıyı çağıran tarafa bırakmanın getirdiği
belirsizlikler. Sözler bu sınırların yanıtı olarak doğacak.

### Geri Çağrılar

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/geri-cagrilar

Hata öncelikli geri çağrı sözleşmesi, sıralı ve birlikte işlemenin elle kurulması, iç içe geçme sorunu ve denetimin çağrılan tarafa devredilmesinin doğurduğu belirsizlikler.

Zamanlayıcılar geri çağrıyı zamana bağladı. Gerçek bir kaynakta bağlanılan şey zaman
değil sonuçtur: ölçüm hazır olduğunda haber ver, olmazsa nedenini bildir. Bu, eşzamansız
programlamanın ilk yaygın arayüzüdür ve sözleri anlamak için önce onun sınırlarını görmek
gerekir.

Bu ders ölçüm kaynağını geri çağrı tabanlı bir arayüze dönüştürür, üç istasyonu önce
sırayla sonra birlikte okur ve bu yaklaşımın nerede yetersiz kaldığını gösterir.

#### Hata Öncelikli Sözleşme

Eşzamansız bir işlev sonucunu `return` ile veremez; çünkü sonuç, işlev döndüğünde henüz
yoktur. Bunun yerine sonucu bir **geri çağrı (callback)** aracılığıyla verir.

Hata da aynı yoldan gelmelidir: önceki derste görüldüğü gibi, geri çağrının içinden
atılan bir hata çağıranın `try` bloğuna ulaşmaz. Yerleşik çözüm, geri çağrının ilk
parametresini hataya ayırmaktır — **hata öncelikli geri çağrı (error-first callback)**
sözleşmesi.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  if (kayit === undefined) {
    setTimeout(() => gericagri(new Error(`bilinmeyen istasyon: ${istasyon}`)), 0);
    return;
  }
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

olcumGetir("A1", (hata, olcum) => {
  if (hata) {
    console.log("A1 hatası:", hata.message);
    return;
  }
  console.log("A1 ölçümü:", olcum.deger);
});

olcumGetir("Z9", (hata, olcum) => {
  if (hata) {
    console.log("Z9 hatası:", hata.message);
    return;
  }
  console.log("Z9 ölçümü:", olcum.deger);
});

console.log("istekler kaydedildi");
```

```
istekler kaydedildi
Z9 hatası: bilinmeyen istasyon: Z9
A1 ölçümü: 21.4
```

Üç ayrıntı sözleşmenin parçasıdır. Birincisi, hata yolunda bile geri çağrı eşzamansız
çağrılır; bilinmeyen istasyon hatası bile `setTimeout` ile bir sonraki tura bırakıldı.
İkincisi, hata varken ikinci parametre anlamsızdır. Üçüncüsü, hata denetimi her çağrı
noktasında elle yazılır — bu, dersin sonunda tekrar edilecek bir maliyettir.

#### Sıralı İşleme ve İç İçe Geçme

Üç istasyonu sırayla okuyup ortalamayı hesaplamak isteyin. Her adımın sonucu bir sonraki
adımın geri çağrısında kullanılabildiği için, adımlar iç içe girer.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  if (kayit === undefined) {
    setTimeout(() => gericagri(new Error(`bilinmeyen istasyon: ${istasyon}`)), 0);
    return;
  }
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

olcumGetir("A1", (hataA, a) => {
  if (hataA) {
    console.log("hata:", hataA.message);
    return;
  }
  olcumGetir("B2", (hataB, b) => {
    if (hataB) {
      console.log("hata:", hataB.message);
      return;
    }
    olcumGetir("C3", (hataC, c) => {
      if (hataC) {
        console.log("hata:", hataC.message);
        return;
      }
      const ortalama = (a.deger + b.deger + c.deger) / 3;
      console.log("sıra:", [a.istasyon, b.istasyon, c.istasyon].join(" -> "));
      console.log("ortalama:", ortalama.toFixed(2));
    });
  });
});
```

```
sıra: A1 -> B2 -> C3
ortalama: 21.43
```

Program doğrudur, ama üç gözlem yapılabilir. Girinti, mantıksal karmaşıklıkla değil adım
sayısıyla artar; on adım on kat girinti demektir. Hata denetimi üç kez, aynı
biçimde tekrarlanır. Son olarak, akışın sonucunu kullanacak kod en içeride kalır; adım
eklemek en dıştan en içe kadar dokunmayı gerektirir.

Bu düzenin yaygın adı geri çağrı piramididir. Asıl sorun görüntü değil, **birleştirilebilir
olmamasıdır**: iki eşzamansız adımı birleştiren genel bir işleç yoktur; her birleşim elle
yazılır.

#### Birlikte İşleme: Eşgüdümün Bedeli

Üç istasyon birbirinden bağımsızsa sırayla beklemek gereksizdir; üçü birlikte
başlatılabilir. Bu durumda sonuçların toplanması, sayaç ve bayrak tutan bir eşgüdüm
işlevi gerektirir.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  if (kayit === undefined) {
    setTimeout(() => gericagri(new Error(`bilinmeyen istasyon: ${istasyon}`)), 0);
    return;
  }
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

function tumunuGetir(istasyonlar, gericagri) {
  const sonuclar = new Array(istasyonlar.length);
  let kalan = istasyonlar.length;
  let bitti = false;

  istasyonlar.forEach((istasyon, dizin) => {
    olcumGetir(istasyon, (hata, olcum) => {
      if (bitti) return;
      if (hata) {
        bitti = true;
        gericagri(hata);
        return;
      }
      console.log("geldi:", istasyon);
      sonuclar[dizin] = olcum;
      kalan -= 1;
      if (kalan === 0) {
        bitti = true;
        gericagri(null, sonuclar);
      }
    });
  });
}

tumunuGetir(["A1", "B2", "C3"], (hata, olcumler) => {
  if (hata) {
    console.log("hata:", hata.message);
    return;
  }
  console.log("sonuç sırası:", olcumler.map((o) => o.istasyon).join(" "));
});
```

```
geldi: B2
geldi: C3
geldi: A1
sonuç sırası: A1 B2 C3
```

Geliş sırası gecikmelere göre B2, C3, A1 oldu; buna karşın sonuç dizisi istek sırasını
korudu. Bunu sağlayan şey, sonuçların dizine göre yerleştirilmesidir — sıraya güvenmek
yerine konum kullanmak, eşzamansız toplamada temel kalıptır.

`bitti` bayrağı da gereklidir: hata sonrası gelen sonuçların geri çağrıyı ikinci kez
tetiklemesini engeller. Yani doğru bir "hepsini bekle" işlevi, sayaç ve bayrak olmak
üzere iki ayrı durum tutar. Bu iş, sözlerle birlikte kütüphaneye taşınacak ve tek satıra
inecek.

#### Denetimin Çağrılana Devri

Geri çağrı verdiğinizde, işlevinizin ne zaman ve kaç kez çalışacağına siz karar vermezsiniz;
çağırdığınız kod karar verir. Buna **denetimin devri (inversion of control)** denir ve üç
somut belirsizlik doğurur.

**Geri çağrı hiç çağrılmayabilir.** Kaynak yanıt vermezse hiçbir şey olmaz; program ne
sonuç ne hata görür, sessizce bekler. Zaman aşımı, bu boşluğu kapatmak için ayrıca kurulur.

**Geri çağrı birden çok kez çağrılabilir.** Sözleşme bunu yasaklar ama dil zorlamaz.

```js
function hataliOlcumGetir(istasyon, gericagri) {
  setTimeout(() => {
    gericagri(null, { istasyon, deger: 21.4 });
    gericagri(null, { istasyon, deger: 21.4 });
  }, 5);
}

let adet = 0;
let toplam = 0;

hataliOlcumGetir("A1", (hata, olcum) => {
  adet += 1;
  toplam += olcum.deger;
  console.log("adet:", adet, "toplam:", toplam.toFixed(1));
});
```

```
adet: 1 toplam: 21.4
adet: 2 toplam: 42.8
```

Tek bir ölçüm iki kez toplandı. Hata, toplayan tarafta değil kaynaktadır; ama sonucuna
toplayan taraf katlanır.

**Geri çağrı bazen eşzamanlı çağrılabilir.** Önbellek isabetinde beklemeye gerek olmadığı
düşünülerek geri çağrının doğrudan çağrılması, akışın sırasını değiştirir.

```js
const KAYNAK = { B2: { gecikme: 10, deger: 19.8 } };

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

const onbellek = new Map();

function onbellekliGetir(istasyon, gericagri) {
  if (onbellek.has(istasyon)) {
    gericagri(null, onbellek.get(istasyon));
    return;
  }
  olcumGetir(istasyon, (hata, olcum) => {
    onbellek.set(istasyon, olcum);
    gericagri(hata, olcum);
  });
}

let durum = "hazırlanıyor";

onbellekliGetir("B2", (hata, olcum) => {
  console.log("1 — ilk çağrı, durum:", durum);
  durum = "işleniyor";
  onbellekliGetir("B2", () => {
    console.log("2 — önbellekten gelen çağrı, durum:", durum);
  });
  durum = "iç çağrıdan sonra";
  console.log("3 — iç çağrıdan sonraki satır, durum:", durum);
});

durum = "hazır";
```

```
1 — ilk çağrı, durum: hazır
2 — önbellekten gelen çağrı, durum: işleniyor
3 — iç çağrıdan sonraki satır, durum: iç çağrıdan sonra
```

İkinci çağrının geri çağrısı, kendisinden sonraki satırdan **önce** çalıştı; çünkü
önbellek isabetinde eşzamanlı çağrıldı. Aynı arayüz, aynı çağrı, iki farklı sıra. Kural
nettir: bir arayüz geri çağrıyı bazen eşzamanlı bazen eşzamansız çağırmamalıdır. Önbellek
isabetinde de sonuç bir sonraki tura bırakılmalıdır.

#### Geri Çağrının Bıraktığı Boşluk

Bu dersteki programlar çalışır ve hiçbirinde dil hatası yoktur. Buna karşın hepsi aynı
eksikleri paylaşır: hata yolu her düzeyde elle yazılır, `try`/`catch` işe yaramaz,
adımların birleştirilmesi için genel bir işleç yoktur ve sözleşme ihlalleri (hiç çağırmama,
iki kez çağırma, eşzamanlı çağırma) dil tarafından engellenmez.

Sonuç, eşzamansız bir işlemin **birinci sınıf bir değer** olmamasıdır. Geri çağrı,
sonucu alan tarafı bildirir; ama işlemin kendisini temsil eden, saklanabilen,
döndürülebilen, başkasına verilebilen bir nesne yoktur.

#### Özet

- Eşzamansız bir işlev sonucunu döndüremez; hata öncelikli geri çağrı sözleşmesi hem
  sonucu hem hatayı tek bir yoldan iletir.
- Sıralı adımlar geri çağrılarla iç içe girer; girinti ve hata denetimi adım sayısıyla
  büyür.
- Birlikte çalışan istekleri toplamak sayaç ve bayrak gerektirir; sonuçlar geliş sırasına
  değil konuma göre yerleştirilir.
- Denetimin devri üç belirsizlik doğurur: geri çağrının hiç çağrılmaması, birden çok kez
  çağrılması ve bazen eşzamanlı çağrılması.
- Geri çağrı düzeninde eşzamansız işlem birinci sınıf bir değer değildir; bu nedenle
  birleştirilemez.

#### Sonraki Adım

Bu boşluğun yanıtı, "sonucu henüz belli olmayan bir işlem"i bir değer olarak temsil
etmektir. Sonraki ders bu değeri tanımlar: söz. Sözün üç durumu, durumların yalnız bir kez
değişebilmesi, geri çağrıların mikro göreve alınması ve zincirlemenin her adımda yeni bir
söz üretmesi — bu dersteki dört eksiğin üçü doğrudan bu kurallardan çıkacak. Dördüncüsü,
işlemin hiç sonuçlanmama olasılığı, açık kalacak; onu kapatan düzenek zaman aşımıdır ve
ilerideki bir derste kurulacak.

## Sözler ve async/await

Sözün durum makinesi ve zincirleme kuralları, birleştiricilerle paralel bekleme, async/await sözdizimsel katmanı, eşzamansız hata yayılımı ve eşzamansız yineleme.

### Söz Kavramı

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/soz-kavrami

Sözün üç durumlu makinesi, sonucun tek kez belirlenmesi, geri çağrıların mikro göreve alınması ve zincirlemenin her adımda yeni bir söz üretmesi.

Önceki ders bir eksikle kapandı: geri çağrı düzeninde eşzamansız işlem bir değer değildir.
Saklanamaz, döndürülemez, başkasına verilemez. Eksiği kapatmak için "sonucu henüz belli
olmayan işlem"i temsil eden bir nesne gerekir.

Bu nesne **söz (promise)** adını taşır. Bu ders sözün durum makinesini, üretim biçimini ve
zincirleme kurallarını kurar; ölçüm kaynağı da bu derste geri çağrı arayüzünden söz
arayüzüne geçer.

#### Üç Durumlu Makine

Bir söz, üç durumdan tam olarak birindedir:

| Durum | Anlamı |
|---|---|
| beklemede (pending) | Sonuç henüz belli değil |
| yerine gelmiş (fulfilled) | Bir değerle sonuçlandı |
| reddedilmiş (rejected) | Bir nedenle sonuçlandı |

Yerine gelmiş ve reddedilmiş durumların ortak adı **sonuçlanmış (settled)** durumdur.
Geçişler tek yönlüdür: beklemede olan bir söz sonuçlanabilir, sonuçlanmış bir söz asla
beklemeye dönmez ve durumunu değiştiremez.

Bu iki kısıt, önceki dersteki belirsizliklerden ikisini doğrudan ortadan kaldırır: sonuç
iki kez bildirilemez, çünkü ikinci bildirim yok sayılır; sonuç bildirildikten sonra
değiştirilemez, çünkü durum kalıcıdır.

#### Söz Üretmek

Söz döndüren bir işlev, işi başlatır ve sonucu temsil eden nesneyi hemen döndürür. Kurucu
işleve verilen fonksiyona **yürütücü (executor)** denir; iki fonksiyon alır: sonucu
belirleyen ve reddeden.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    if (kayit === undefined) {
      setTimeout(() => red(new Error(`bilinmeyen istasyon: ${istasyon}`)), 0);
      return;
    }
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

olcumGetir("B2").then(
  (olcum) => console.log("yerine geldi:", olcum.istasyon, olcum.deger),
  (hata) => console.log("reddedildi:", hata.message),
);

olcumGetir("D4").then(
  (olcum) => console.log("yerine geldi:", olcum.istasyon, olcum.deger),
  (hata) => console.log("reddedildi:", hata.message),
);

console.log("iki söz de beklemede");
```

```
iki söz de beklemede
yerine geldi: B2 19.8
reddedildi: D4: sensör arızası
```

Kaynak tablosuna arızalı bir istasyon eklendi; `D4` her çağrıda reddeder. Kursun geri
kalanında hata yollarını sınamak için bu istasyon kullanılacak.

Yürütücünün gövdesi **eşzamanlı** çalışır; `olcumGetir` çağrısı dönmeden zamanlayıcı
kurulur. Dönen değer ise beklemede bir sözdür. Bu nedenle son satırdaki mesaj ilk yazılır.

Bir noktaya dikkat edilmelidir: iş, söz oluşturulduğunda başlar. Söz "başlatılacak iş"in
değil, "başlamış işin sonucunun" temsilidir. Sonraki derste bu ayrımın pratik sonucu
görülecek.

#### Sonuç Tek Kez Belirlenir

Durum makinesinin kalıcılığı dil tarafından güvence altındadır. Yürütücü içinde kaç kez
karar verilirse verilsin, ilk karar geçerlidir.

```js
const soz = new Promise((karar, red) => {
  karar("ilk karar");
  karar("ikinci karar");
  red(new Error("geç reddetme"));
});

soz.then(
  (deger) => console.log("değer:", deger),
  (hata) => console.log("hata:", hata.message),
);
```

```
değer: ilk karar
```

İkinci karar ve reddetme sessizce yok sayıldı. Geri çağrı düzeninde elle yazılan `bitti`
bayrağının işini burada dilin kendisi yapar.

#### Geri Çağrılar Her Zaman Mikro Görevdir

Söz sonuçlanmış olsa bile `then` geri çağrısı hemen çalışmaz; mikro görev kuyruğuna
alınır. Bu, önceki konudaki "bazen eşzamanlı, bazen eşzamansız" belirsizliğini ortadan
kaldırır.

```js
const soz = Promise.resolve("hazır değer");

soz.then((deger) => console.log("2 — ilk then:", deger));
soz.then((deger) => console.log("3 — ikinci then:", deger));
console.log("1 — eşzamanlı");
queueMicrotask(() => console.log("4 — sonra kaydedilen mikro görev"));
```

```
1 — eşzamanlı
2 — ilk then: hazır değer
3 — ikinci then: hazır değer
4 — sonra kaydedilen mikro görev
```

`Promise.resolve` ile üretilen söz daha yazıldığı anda yerine gelmişti; buna karşın geri
çağrıları eşzamanlı kodun tamamından sonra çalıştı. Aynı söze birden çok `then` bağlanabilir
ve hepsi kayıt sırasına göre, aynı mikro görev boşaltmasında çalışır.

Bu kural bir de yan sonuç doğurur: söz geri çağrıları mikro görev olduğundan, birbirini
zincirleyen uzun bir söz dizisi görev kuyruğunu bekletebilir. Önceki konudaki açlık uyarısı
sözler için de geçerlidir.

#### Zincirleme: Her Adım Yeni Bir Söz

`then` çağrısı, sözü değiştirmez; **yeni bir söz** döndürür. Yeni sözün sonucu, geri
çağrının ne döndürdüğüne bağlıdır:

- Sıradan bir değer döndürürse, yeni söz o değerle yerine gelir.
- Bir söz döndürürse, yeni söz o sözün sonucunu bekler ve onunla sonuçlanır.
- Hata atarsa, yeni söz o hatayla reddedilir.

Reddetme, kendisini işleyen bir geri çağrı bulana kadar zincirde aşağı akar. `catch`
yalnızca reddetmeyi işleyen bir `then` yazımıdır; `finally` ise sonucu değiştirmeden her
iki durumda da çalışır.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
};

function olcumGetir(istasyon) {
  return new Promise((karar) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => karar({ istasyon, deger: kayit.deger }), kayit.gecikme);
  });
}

olcumGetir("B2")
  .then((olcum) => {
    console.log("1 — ham ölçüm:", olcum.deger);
    return olcum.deger;
  })
  .then((deger) => {
    if (deger > 25) throw new Error("değer aralık dışı");
    return Math.round(deger);
  })
  .then((yuvarlanmis) => {
    console.log("2 — yuvarlanmış:", yuvarlanmis);
    return olcumGetir("A1");
  })
  .then((olcum) => {
    console.log("3 — zincirin ikinci ölçümü:", olcum.istasyon, olcum.deger);
    throw new Error("işleme adımında hata");
  })
  .catch((hata) => {
    console.log("4 — yakalandı:", hata.message);
    return "kurtarma değeri";
  })
  .then((deger) => console.log("5 — catch sonrası zincir sürüyor:", deger))
  .finally(() => console.log("6 — finally her durumda çalışır"));
```

```
1 — ham ölçüm: 19.8
2 — yuvarlanmış: 20
3 — zincirin ikinci ölçümü: A1 21.4
4 — yakalandı: işleme adımında hata
5 — catch sonrası zincir sürüyor: kurtarma değeri
6 — finally her durumda çalışır
```

Üçüncü adım bir söz döndürdü ve zincir onun sonuçlanmasını bekledi; dördüncü adımdaki hata
`catch`e ulaştı; `catch` bir değer döndürdüğü için zincir yeniden yerine gelmiş duruma
döndü ve devam etti.

Son davranış sık yanılgı üretir: `catch` hatayı yutar ve zinciri onarır. Hatanın devam
etmesi isteniyorsa `catch` içinde yeniden atılmalıdır.

Sonucu bir söz olan her nesne bu zincire katılabilir. Standart, `then` adında bir yöntemi
olan her nesneyi bu amaçla kabul eder; böyle nesnelere **then taşıyan nesne (thenable)**
denir. Zincire giren böyle bir nesne, gerçek bir sözle aynı biçimde ele alınır.

#### Geri Çağrıya Göre Ne Değişti

Önceki dersin dört eksiğinden üçü doğrudan kapandı. Sonuç artık bir değerdir: saklanabilir,
döndürülebilir, bir diziye konabilir. Hata yolu tek bir `catch` ile zincirin tamamı için
yazılabilir. Sözleşme ihlalleri dil tarafından engellenir — iki kez karar verilemez, geri
çağrı eşzamanlı çağrılamaz.

Kapanmayan tek eksik, işlemin hiç sonuçlanmama olasılığıdır. Beklemede kalan bir söz
sonsuza kadar beklemede kalabilir; bunu çözen düzenek zaman aşımıdır ve ilerideki bir
derste kurulacak.

#### Özet

- Söz, sonucu henüz belli olmayan bir işlemi temsil eden değerdir; üç durumu vardır ve
  sonuçlandıktan sonra durumu değişmez.
- İş, söz oluşturulduğunda başlar; söz başlamış bir işin sonucunun temsilidir.
- Yürütücü eşzamanlı çalışır, ama `then` geri çağrıları her zaman mikro göreve alınır.
- `then` yeni bir söz döndürür; değer döndürmek zinciri sürdürür, söz döndürmek onu
  bekletir, hata atmak reddetmeyi aşağı akıtır.
- `catch` reddetmeyi işler ve zinciri yerine gelmiş duruma döndürür; hatanın sürmesi
  isteniyorsa yeniden atılmalıdır.

#### Sonraki Adım

Tek bir söz artık bir değer olduğuna göre, birden çok söz bir koleksiyon olarak ele
alınabilir. Sonraki ders, üç istasyonu birlikte bekleyen, ilk yanıtı alan veya ilk başarılı
yanıtı seçen birleştiricileri tanıtır; önceki konuda sayaç ve bayrakla elle yazılan
eşgüdüm işlevi tek satıra inecek. Aynı derste, birleştiricilerin hata karşısındaki farklı
davranışları da karşılaştırılacak.

### Söz Birleştiricileri

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/soz-birlestiricileri

Birden çok sözü tek sözde toplayan dört birleştirici; hata karşısındaki davranış farkları; işin ne zaman başladığı ile ne zaman beklendiğinin ayrımı.

Önceki ders tek bir sözü bir değer haline getirdi. Değer olan şey diziye konabilir; diziye
konan şey topluca ele alınabilir. Geri çağrı konusunda sayaç ve bayrakla elle yazılan
"hepsini bekle" işlevi, artık dilin sağladığı bir işleçle yazılabilir.

Bu ders dört birleştiriciyi tanıtır ve aralarındaki asıl farkın hata karşısındaki davranış
olduğunu gösterir. Ölçüm akışı burada üç istasyonu birlikte okuyan bir toplayıcıya dönüşür.

#### İş Ne Zaman Başlar

Birleştiricileri doğru okumak için önce bir yanlış anlamanın giderilmesi gerekir:
**birleştiriciler işi başlatmaz.** İş, sözler oluşturulduğunda başlamıştır; birleştirici
yalnız beklemeyi düzenler.

Aşağıdaki örneklerde ölçüm kaynağı, isteğin başladığı anı da yazdırır. Kurs boyunca
kullanılan tablo, arızalı `D4` istasyonunu da içerir.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

const olcumler = await Promise.all([olcumGetir("A1"), olcumGetir("B2"), olcumGetir("C3")]);
console.log("sonuç:", olcumler.map((o) => `${o.istasyon}=${o.deger}`).join(" "));
```

```
istek başladı: A1
istek başladı: B2
istek başladı: C3
sonuç: A1=21.4 B2=19.8 C3=23.1
```

Üç istek de `Promise.all` çağrılmadan önce, dizi hazırlanırken başladı. Beklemeler üst
üste bindi; sonuç, en yavaş istek olan A1 tamamlandığında geldi.

Sonuç dizisi **istek sırasını** korur, geliş sırasını değil. Geri çağrı konusunda bunu
sağlamak için sonuçları dizine göre yerleştirmek gerekiyordu; burada güvence
birleştiricinin kendisindedir.

#### Hepsini Bekle: `Promise.all`

`Promise.all`, verilen sözlerin tümü yerine geldiğinde, sonuç dizisiyle yerine gelir.
Herhangi biri reddedilirse, **ilk reddetmeyle birlikte hemen** reddedilir.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

try {
  await Promise.all([olcumGetir("A1"), olcumGetir("D4"), olcumGetir("C3")]);
} catch (hata) {
  console.log("all reddedildi:", hata.message);
}
```

```
istek başladı: A1
istek başladı: D4
istek başladı: C3
all reddedildi: D4: sensör arızası
```

İki nokta önemlidir. Birincisi, sonuç on beşinci milisaniyede belli oldu; A1 ve C3
beklenmedi. İkincisi ve daha önemlisi: **beklenmemek, iptal edilmek değildir.** A1 ve C3
istekleri sürmeye devam eder ve sonuçları da üretilir; yalnız kimse onlara bakmaz. İptal
ayrı bir düzenek gerektirir ve ilerideki bir derste kurulacak.

Bu davranış, "hepsi olmazsa hiçbiri" anlamı taşıyan işler için doğrudur: üç ölçümün
ortalaması, biri eksikken hesaplanamaz.

#### Hepsinin Sonucunu Topla: `Promise.allSettled`

Kısmi sonuç kabul edilebilir olduğunda `all` yanlış araçtır. `Promise.allSettled` hiçbir
zaman reddedilmez; her söz için durumu ve sonucunu taşıyan bir kayıt döndürür.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

const sonuclar = await Promise.allSettled([olcumGetir("A1"), olcumGetir("D4"), olcumGetir("C3")]);
for (const s of sonuclar) {
  if (s.status === "fulfilled") console.log("yerine geldi:", s.value.istasyon, s.value.deger);
  else console.log("reddedildi:", s.reason.message);
}
```

```
istek başladı: A1
istek başladı: D4
istek başladı: C3
yerine geldi: A1 21.4
reddedildi: D4: sensör arızası
yerine geldi: C3 23.1
```

Arızalı istasyon akışı durdurmadı; iki geçerli ölçüm elde edildi ve hata da kaydedildi.
Ölçüm ağı gibi kısmi verinin işe yaradığı alanlarda varsayılan seçim budur.

Kayıtların alan adları sabittir: yerine gelmiş kayıtta `status` ve `value`, reddedilmiş
kayıtta `status` ve `reason` bulunur.

#### İlk Sonuçlanan: `Promise.race`

`Promise.race`, hangi söz önce sonuçlanırsa onun sonucunu alır — yerine gelme ile reddetme
arasında ayrım yapmaz.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

const ilk = await Promise.race([olcumGetir("A1"), olcumGetir("B2"), olcumGetir("C3")]);
console.log("race sonucu:", ilk.istasyon, ilk.deger);

try {
  await Promise.race([olcumGetir("A1"), olcumGetir("D4")]);
} catch (hata) {
  console.log("race reddedildi:", hata.message);
}
```

```
istek başladı: A1
istek başladı: B2
istek başladı: C3
race sonucu: B2 19.8
istek başladı: A1
istek başladı: D4
race reddedildi: D4: sensör arızası
```

İkinci yarışta en hızlı sonuçlanan söz arızalı istasyonunkiydi; `race` onu da sonuç
saydı. Bu nedenle `race`, "en hızlı sağlıklı yanıtı al" anlamına gelmez.

Bir uyarı gerekir: yarışı kaybeden sözler yaşamaya devam eder. Hiç sonuçlanmayan bir söz
`race` içine konursa, o söz ve kapattığı bütün değerler program boyunca bellekte kalır.
Bu, bellek konusunda ele alınacak sızıntı türlerinden biridir.

#### İlk Başarılı: `Promise.any`

`Promise.any`, reddetmeleri yok sayar ve **ilk yerine gelen** sonucu döndürür. Hepsi
reddedilirse, tüm nedenleri içeren bir toplu hatayla reddedilir.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

const ilkBasarili = await Promise.any([olcumGetir("A1"), olcumGetir("D4")]);
console.log("any sonucu:", ilkBasarili.istasyon, ilkBasarili.deger);

try {
  await Promise.any([olcumGetir("D4")]);
} catch (hata) {
  console.log("any reddedildi:", hata.constructor.name);
  console.log("içindeki hatalar:", hata.errors.map((h) => h.message).join(", "));
}
```

```
istek başladı: A1
istek başladı: D4
any sonucu: A1 21.4
istek başladı: D4
any reddedildi: AggregateError
içindeki hatalar: D4: sensör arızası
```

Arızalı istasyon on beşinci milisaniyede reddetti; `any` bunu yok sayıp otuzuncu
milisaniyedeki geçerli yanıtı bekledi. Toplu hata nesnesi, tek tek nedenleri `errors`
dizisinde taşır — böylece hiçbir hata bilgisi kaybolmaz.

Bu birleştirici, aynı veriyi birden çok kaynaktan isteyip ilk çalışanı kullanan yedeklilik
kalıbının doğrudan karşılığıdır.

#### Seçim Ölçütü

| Birleştirici | Yerine gelir | Reddedilir | Uygun olduğu durum |
|---|---|---|---|
| `all` | Hepsi yerine gelirse | İlk reddetmede | Bütün parçalar zorunlu |
| `allSettled` | Her zaman | Hiçbir zaman | Kısmi sonuç kabul edilebilir |
| `race` | İlk sonuçlanan yerine gelmişse | İlk sonuçlanan reddedilmişse | Zaman sınırı koymak |
| `any` | İlk yerine gelende | Hepsi reddedilirse | Yedekli kaynaklar |

Dördü de aynı girdi biçimini alır ve tek bir söz döndürür; bu nedenle iç içe
kullanılabilirler. Bir istasyon kümesinin `any` ile seçilen sonucu, başka bir kümenin
sonucuyla `all` içinde birleştirilebilir.

#### Özet

- Birleştiriciler işi başlatmaz; sözler oluşturulduğunda iş başlamıştır ve
  birleştirici yalnız beklemeyi düzenler.
- `all` sonuç dizisini istek sırasında verir ve ilk reddetmede hemen reddedilir;
  beklenmeyen istekler iptal edilmez, sürmeye devam eder.
- `allSettled` hiç reddedilmez; her söz için durum ve sonuç kaydı döndürür.
- `race` ilk sonuçlanana bakar, başarı ile başarısızlığı ayırt etmez; `any` ilk yerine
  geleni seçer ve hepsi reddedilirse nedenleri toplu bir hatada taşır.
- Yarışı kaybeden ve sonuçlanmayan sözler bellekte kalmaya devam eder.

#### Sonraki Adım

Birleştiriciler beklemenin yapısını kurdu, ama kodun okunuşunu değiştirmedi: sonuçlar
hâlâ geri çağrıların içinde işleniyor. Sonraki ders, aynı zincirleri sıralı bir yazımla
ifade eden sözdizimsel katmanı tanıtır — `async` ve `await`. Aynı derste, bu yazımın
farkında olmadan sıralı beklemeye yol açtığı yaygın hata da gösterilecek ve bu derste
kurulan birleştiricilerle düzeltilecek.

### async/await

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/async-await

async fonksiyonun her zaman söz döndürmesi, await'in fonksiyonu askıya alması, sözdizimsel katmanın söz zincirine karşılığı ve farkında olmadan sıralı beklemeye düşme hatası.

Sözler eşzamansız işlemi bir değere dönüştürdü, birleştiriciler de beklemeyi düzenledi.
Buna karşın kodun okunuşu değişmedi: her adımın sonucu hâlâ bir geri çağrının içinde
işleniyor ve akış, kaynak metinde yukarıdan aşağı akmıyor.

Bu ders, aynı söz zincirlerini sıralı bir yazımla ifade eden katmanı kurar. Vurgulanacak
nokta baştan söylenmelidir: `async` ve `await` yeni bir eşzamansızlık düzeneği getirmez.
İkisi de sözler üzerine kurulmuş **sözdizimsel bir katmandır**; olay döngüsü kuralları
aynen geçerlidir.

#### `async` Her Zaman Söz Döndürür

`async` anahtar sözcüğüyle tanımlanan bir fonksiyon, ne döndürürse döndürsün bir söz
döndürür. Sıradan bir değer döndürürse, söz o değerle yerine gelir.

```js
async function sabitOlcum() {
  return { istasyon: "A1", deger: 21.4 };
}

const donen = sabitOlcum();
console.log("dönen değer bir söz mü:", donen instanceof Promise);
console.log("beklenmiş sonuç:", await donen);
```

```
dönen değer bir söz mü: true
beklenmiş sonuç: { istasyon: 'A1', deger: 21.4 }
```

Bunun tersi de doğrudur: `async` fonksiyonun gövdesinde atılan bir hata, dışarıya
fırlamaz; dönen sözü reddeder. Hata yayılımının ayrıntısı sonraki dersin konusudur.

#### `await` Fonksiyonu Askıya Alır, İş Parçacığını Değil

`await` bir sözün sonuçlanmasını bekler. Beklerken **yalnız içinde bulunduğu fonksiyonu**
askıya alır; iş parçacığı serbest kalır ve çağıran koda geri döner.

Bunun gözlenebilir sonucu şudur: `async` bir fonksiyonun gövdesi, ilk `await` ifadesine
kadar eşzamanlı çalışır; oradan sonrası bir mikro göreve dönüşür.

```js
async function olcumIsle() {
  console.log("2 — async gövde eşzamanlı başlar");
  await null;
  console.log("4 — ilk await sonrası mikro görevde sürer");
}

console.log("1 — çağrıdan önce");
olcumIsle();
console.log("3 — çağrıdan sonra");
```

```
1 — çağrıdan önce
2 — async gövde eşzamanlı başlar
3 — çağrıdan sonra
4 — ilk await sonrası mikro görevde sürer
```

Beklenen değer bir söz bile değildi; `await null` da bir mikro görev turu ekler. Kural
biçimsel olarak şudur: `await` ifadesi, beklenen değeri `Promise.resolve` ile sarar ve
devamını o sözün geri çağrısı olarak kaydeder.

İki `async` fonksiyon iç içe değil, yan yana ilerleyebilir. Her `await`, denetimi olay
döngüsüne bırakır ve diğer fonksiyonun sıradaki adımı araya girer:

```js
async function ilkAkis() {
  console.log("A — adım 1");
  await null;
  console.log("A — adım 2");
  await null;
  console.log("A — adım 3");
}

async function ikinciAkis() {
  console.log("B — adım 1");
  await null;
  console.log("B — adım 2");
  await null;
  console.log("B — adım 3");
}

ilkAkis();
ikinciAkis();
console.log("eşzamanlı kısım bitti");
```

```
A — adım 1
B — adım 1
eşzamanlı kısım bitti
A — adım 2
B — adım 2
A — adım 3
B — adım 3
```

İki akış adım adım birbirine geçti; buna karşın hiçbir an ikisi aynı anda çalışmadı. Tek
iş parçacıklı model bozulmadı — yalnız bölünme noktaları `await` ifadeleriyle açıkça
işaretlendi.

#### Zincirin Karşılığı

Aynı akış iki yazımla da ifade edilebilir; ürettikleri söz ve sonuç aynıdır.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

async function ortalamaAl() {
  const a = await olcumGetir("A1");
  const b = await olcumGetir("B2");
  return (a.deger + b.deger) / 2;
}

function ortalamaAlZincir() {
  return olcumGetir("A1").then((a) =>
    olcumGetir("B2").then((b) => (a.deger + b.deger) / 2),
  );
}

console.log("await yazımı:", (await ortalamaAl()).toFixed(2));
console.log("zincir yazımı:", (await ortalamaAlZincir()).toFixed(2));
```

```
istek başladı: A1
istek başladı: B2
await yazımı: 20.60
istek başladı: A1
istek başladı: B2
zincir yazımı: 20.60
```

İki yazımın farkı okunabilirliktedir. `await` yazımında ara sonuçlar sıradan yerel
değişkenlerdir; ikinci adımda birinci adımın sonucuna erişmek için iç içe geçmek
gerekmez. Adım sayısı arttıkça fark büyür: zincir yazımında her ek adım ya yeni bir
girinti ya da elle taşınan bir ara nesne ister.

#### Sıralı Bekleme Tuzağı

`await` sıralı bir okunuş verir ve bu okunuş yanıltıcı olabilir: art arda yazılmış
beklemeler gerçekten art arda yürür.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

for (const istasyon of ["A1", "B2", "C3"]) {
  const olcum = await olcumGetir(istasyon);
  console.log("geldi:", olcum.istasyon, olcum.deger);
}
```

```
istek başladı: A1
geldi: A1 21.4
istek başladı: B2
geldi: B2 19.8
istek başladı: C3
geldi: C3 23.1
```

Çıktıdaki sıra tanıyı verir: her istek, bir öncekinin sonucu geldikten **sonra** başlıyor.
Üç istasyon birbirinden bağımsız olduğu halde beklemeler üst üste binmiyor; toplam süre
üç gecikmenin toplamı kadar.

Düzeltme, isteklerin başlatılmasını beklemekten ayırmaktır. Sözler oluşturulduğunda iş
başladığına göre, önce hepsi oluşturulur, sonra tek noktada beklenir.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  console.log("istek başladı:", istasyon);
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

const sozler = ["A1", "B2", "C3"].map((istasyon) =>
  olcumGetir(istasyon).then((olcum) => {
    console.log("geldi:", olcum.istasyon, olcum.deger);
    return olcum;
  }),
);
const olcumler = await Promise.all(sozler);
console.log("toplam:", olcumler.reduce((t, o) => t + o.deger, 0).toFixed(1));
```

```
istek başladı: A1
istek başladı: B2
istek başladı: C3
geldi: B2 19.8
geldi: C3 23.1
geldi: A1 21.4
toplam: 64.3
```

Üç istek de hemen başladı; sonuçlar gecikme sırasına göre geldi. Toplam bekleme, üç
gecikmenin toplamı değil en büyüğü kadar.

Ölçüt basit bir sorudur: **bu adım, bir öncekinin sonucuna gerçekten muhtaç mı?** Yanıt
hayırsa döngü içindeki `await` bir hatadır. Yanıt evetse — örneğin ikinci istasyonun
kimliği birinci ölçümden çıkıyorsa — sıralı bekleme doğrudur ve zorunludur.

#### Üst Düzey `await`

Modül düzeyinde, bir fonksiyonun içinde olmadan da `await` kullanılabilir. Bu dersteki
örneklerin son satırları bunu kullanır. Modül biçimlerine bağlı ayrıntılar Modüller,
Araçlar ve Ekosistem kursunun konusudur; burada bilinmesi gereken tek şey, üst düzey
beklemenin modülün değerlendirilmesini geciktirdiğidir.

#### Özet

- `async` fonksiyon her zaman söz döndürür; gövdesinde atılan hata dönen sözü reddeder.
- `await` yalnız kendi fonksiyonunu askıya alır; iş parçacığı serbest kalır ve çağıran
  koda dönülür.
- Gövde ilk `await` ifadesine kadar eşzamanlı çalışır, sonrası mikro göreve dönüşür; söz
  olmayan bir değeri beklemek bile bir tur ekler.
- `await` yazımı ile zincir yazımı aynı sözü üretir; fark okunabilirliktedir.
- Bağımsız işler için döngü içinde `await` kullanmak beklemeleri sıraya sokar; doğru
  kalıp, işleri başlatıp tek noktada beklemektir.

#### Sonraki Adım

Bu derste hata yolu bilinçli olarak dışarıda bırakıldı. Sonraki ders onu ele alır:
`async` gövdesinde atılan hatanın nereye gittiği, `try`/`catch` ile `catch` yazımının ne
zaman ayrıştığı, birleştiricilerin hangi durumda hata bilgisini kaybettiği ve hiç kimsenin
işlemediği bir reddetmenin çalışma zamanına nasıl bildirildiği. Ölçüm akışı da orada
arızalı istasyona dayanıklı hale gelecek.

### Eşzamansız Hata Yönetimi

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/eszamansiz-hata-yonetimi

Reddetmenin zincirde yayılması, try/catch ile catch yazımının ayrıştığı noktalar, hata bilgisinin kaybı, neden zinciri ve işlenmeyen reddetmelerin izlenmesi.

Önceki ders hata yolunu bilinçli olarak dışarıda bıraktı. Oysa eşzamansız kodda hata,
eşzamanlı koddakinden farklı davranır: yığıt çoktan boşalmış olduğu için hatanın
"yukarıya" fırlayacağı bir çağıran yoktur. Reddetme, yığıt yerine **sözün kendisi**
üzerinden taşınır.

Bu ders reddetmenin nasıl yayıldığını, hangi yazımın hangi hatayı yakaladığını ve hiç
kimsenin işlemediği bir reddetmenin ne olduğunu kurar. Ölçüm akışı burada arızalı
istasyonlara dayanıklı hale gelir.

#### Reddetme `try`/`catch` ile Yakalanır

`await` ile beklenen bir söz reddedilirse, bekleme noktasında bir hata atılmış gibi
davranılır. Bu, `try`/`catch`/`finally` yapısını eşzamansız kodda yeniden kullanılabilir
kılan tek kuraldır.

Kaynak tablosuna ikinci bir arızalı istasyon eklendi; `E5` daha kısa gecikmeyle
reddediyor. Bu, hata sırasının önemli olduğu örneklerde kullanılacak.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
  E5: { gecikme: 5, hata: "bağlantı yok" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

try {
  const olcum = await olcumGetir("D4");
  console.log("bu satır çalışmaz:", olcum.deger);
} catch (hata) {
  console.log("yakalandı:", hata.message);
} finally {
  console.log("finally: kaynak serbest bırakıldı");
}
```

```
yakalandı: D4: sensör arızası
finally: kaynak serbest bırakıldı
```

`finally` bloğu eşzamanlı koddaki anlamını korur: sonuç ne olursa olsun çalışır ve
sonucu değiştirmez. Açılmış bir kaynağın kapatılması, kurulmuş bir zamanlayıcının
iptali gibi temizlik işleri buraya yazılır.

#### `then`in İkinci Parametresi ile `catch` Aynı Şey Değil

Zincir yazımında hata işleyici iki yere konabilir ve ikisi farklı şeyler yakalar.
`then(basari, hata)` biçiminde verilen ikinci geri çağrı, yalnız **kendisinden önceki**
sözün reddetmesini işler; aynı `then` çağrısındaki başarı geri çağrısının attığı hatayı
işlemez.

```js
Promise.resolve("ham değer")
  .then(
    () => {
      throw new Error("işleme adımında hata");
    },
    (hata) => console.log("bu geri çağrı çalışmaz:", hata.message),
  )
  .catch((hata) => console.log("zincirin sonunda yakalandı:", hata.message));
```

```
zincirin sonunda yakalandı: işleme adımında hata
```

Kural şudur: hata işleyici, hatanın oluştuğu adımdan **sonra** gelmelidir. Bu nedenle
varsayılan seçim, zincirin sonuna tek bir `catch` koymaktır; iki parametreli biçim ancak
"bu adımın kendi hatasını başkası işlesin" denmek istendiğinde anlamlıdır.

#### Bekleyerek Döndürmek

`async` bir fonksiyonda `return olcumGetir(...)` ile `return await olcumGetir(...)`
çoğu bağlamda aynı sonucu verir. `try` bloğunun içinde vermez.

```js
const KAYNAK = {
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

async function beklemedenDondur(istasyon) {
  try {
    return olcumGetir(istasyon);
  } catch (hata) {
    return "catch çalıştı";
  }
}

async function bekleyerekDondur(istasyon) {
  try {
    return await olcumGetir(istasyon);
  } catch (hata) {
    return "catch çalıştı";
  }
}

console.log("bekleyerek:", await bekleyerekDondur("D4"));
try {
  await beklemedenDondur("D4");
} catch (hata) {
  console.log("beklemeden: hata dışarı sızdı —", hata.message);
}
```

```
bekleyerek: catch çalıştı
beklemeden: hata dışarı sızdı — D4: sensör arızası
```

Beklenmeden döndürülen sözde fonksiyon, söz sonuçlanmadan önce geri döner; `try` bloğu o
anda kapanmıştır ve reddetme çağırana geçer. Kural: **bir sözü kendi `try` bloğunuzda
yakalamak istiyorsanız, döndürmeden önce beklemelisiniz.**

#### Hata Bilgisinin Kaybı

Birleştiriciler hatayı farklı biçimlerde taşır. `Promise.all` yalnız ilk reddetmeyi
verir; diğerleri sessizce kaybolur.

```js
const KAYNAK = {
  D4: { gecikme: 15, hata: "sensör arızası" },
  E5: { gecikme: 5, hata: "bağlantı yok" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

try {
  await Promise.all([olcumGetir("D4"), olcumGetir("E5")]);
} catch (hata) {
  console.log("all yalnız ilk hatayı verir:", hata.message);
}

const sonuclar = await Promise.allSettled([olcumGetir("D4"), olcumGetir("E5")]);
const hatalar = sonuclar.filter((s) => s.status === "rejected").map((s) => s.reason.message);
console.log("allSettled hepsini verir:", hatalar.join(" | "));
```

```
all yalnız ilk hatayı verir: E5: bağlantı yok
allSettled hepsini verir: D4: sensör arızası | E5: bağlantı yok
```

İki istasyon da arızalıydı; `all` yalnız daha erken reddedeni bildirdi. Tanı için bütün
hataların gerekli olduğu durumlarda — örneğin hangi istasyonların düştüğünü kütüğe yazmak
gerektiğinde — `allSettled` kullanılmalıdır.

#### Neden Zinciri

Bir hatayı yakalayıp yerine daha anlamlı bir hata atmak yaygındır; ancak kök neden
kaybolursa tanı zorlaşır. Hata nesnesinin `cause` seçeneği, alt düzey hatayı üst düzey
hataya iliştirir.

```js
const KAYNAK = {
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

async function olcumOku(istasyon) {
  try {
    return await olcumGetir(istasyon);
  } catch (hata) {
    throw new Error(`istasyon okunamadı: ${istasyon}`, { cause: hata });
  }
}

try {
  await olcumOku("D4");
} catch (hata) {
  console.log("üst düzey hata:", hata.message);
  console.log("kök neden:", hata.cause.message);
}
```

```
üst düzey hata: istasyon okunamadı: D4
kök neden: D4: sensör arızası
```

Bu kalıp, katmanlar arasında bağlam eklerken bilgi kaybını önler: üst katman "hangi
istasyon" der, alt katman "neden" der, ikisi tek nesnede taşınır.

#### İşlenmeyen Reddetmeler

Bir söz reddedilir ve ona hiçbir hata işleyici bağlanmamışsa, ortaya hiçbir yerde
yakalanmayan bir hata çıkar. Çalışma zamanı bunu izler ve bildirir.

```js
const KAYNAK = {
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

process.on("unhandledRejection", (neden) => {
  console.log("1 — işlenmeyen reddetme bildirildi:", neden.message);
});

process.on("rejectionHandled", () => {
  console.log("3 — reddetme sonradan işlendi");
});

const soz = olcumGetir("D4");
setTimeout(() => soz.catch((hata) => console.log("2 — geç yakalama:", hata.message)), 50);
```

```
1 — işlenmeyen reddetme bildirildi: D4: sensör arızası
2 — geç yakalama: D4: sensör arızası
3 — reddetme sonradan işlendi
```

İşleyici sonradan bağlandı ve hata gerçekten yakalandı; ama çalışma zamanı bu arada
"işlenmeyen reddetme" bildirimini çoktan yapmıştı. Bildirim, mikro görev kuyruğu
boşaldığında hâlâ işleyicisiz kalan reddetmeler için üretilir.

Bunun iki pratik sonucu vardır. Birincisi, hata işleyici sözü *oluşturduğunuz* yerde
bağlanmalıdır; sonraya bırakmak yanlış alarm üretir. İkincisi, dinleyici hiç
kaydedilmemişse çalışma zamanı varsayılan davranışını uygular: sunucu ortamında tanı
iletisi yazılır ve süreç sıfırdan farklı bir çıkış koduyla sonlanır. Bu nedenle
işlenmeyen reddetme, kütüğe yazılıp geçilecek bir uyarı değil, düzeltilmesi gereken bir
kusurdur.

#### Dayanıklı Ölçüm Toplama

Bu dersin araçları birleştirildiğinde, ölçüm akışının arızaya dayanıklı biçimi ortaya
çıkar: her istasyon için sonuç ya da nedeni bilinen bir başarısızlık üretilir, hiçbir hata
sessizce kaybolmaz ve toplama tek bir arıza yüzünden durmaz. Kalan tek boşluk, hiç yanıt
vermeyen bir istasyondur; onu ne `catch` ne `allSettled` çözer, çünkü ortada reddedilen
bir söz yoktur. Bu boşluk zaman aşımı düzeneğiyle kapatılacak.

#### Özet

- Reddetme yığıt üzerinden değil söz üzerinden taşınır; `await` ile beklenen bir söz
  reddedildiğinde bekleme noktasında hata atılmış gibi davranılır.
- `then`in ikinci parametresi yalnız önceki adımın hatasını işler; zincirin sonuna konan
  `catch` kendinden önceki tüm adımları kapsar.
- Bir sözü kendi `try` bloğunda yakalamak için döndürmeden önce beklemek gerekir.
- `all` yalnız ilk reddetmeyi taşır; bütün hatalar gerekiyorsa `allSettled` kullanılır.
- `cause` seçeneği kök nedeni koruyarak hata sarmalamayı mümkün kılar.
- İşlenmeyen reddetme, mikro görev kuyruğu boşaldığında işleyicisiz kalan reddetmedir ve
  çalışma zamanı tarafından bildirilir.

#### Sonraki Adım

Buraya kadar her ölçüm tek seferlik bir sonuçtu: iste, bekle, al. Ölçüm istasyonları ise
sürekli veri üretir; sonucu tek bir söz değil, ucu açık bir dizi temsil eder. Sonraki ders
bu diziyi ele alır: eşzamansız yineleyici protokolü, `for await` döngüsü, eşzamansız
üreteçler ve tüketicinin üreticiden yavaş olduğu durumda ortaya çıkan geri basınç sorunu.

### Eşzamansız Yineleme

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/eszamansiz-yineleme

Eşzamansız yineleyici protokolü, for await döngüsü, eşzamansız üreteçler, erken çıkışta temizlik ve çekmeli akışın geri basınç karşısındaki üstünlüğü.

Buraya kadar her ölçüm tek seferlik bir sonuçtu: iste, bekle, al. Bir istasyon ise sürekli
veri üretir. Sonucu tek bir söz değil, ucu açık bir dizi temsil eder — ve bu dizinin
elemanları zaman içinde, birer birer gelir.

Bu ders o diziyi ele alır. Sıradan yineleme protokolü burada yetersiz kalır: bir sonraki
elemanın hazır olması beklenmelidir, oysa `next` çağrısı değeri hemen döndürmek zorundadır.
Çözüm, protokolü söz döndürecek biçimde genişletmektir.

#### Eşzamansız Yineleyici Protokolü

JavaScript'te Nesneler ve Fonksiyonlar kursunda tanıtılan yineleme protokolü, `next`
çağrısında `value` ve `done` alanları taşıyan bir nesne bekler. Eşzamansız protokolde
tek fark, `next` çağrısının böyle bir nesneyle **yerine gelen bir söz** döndürmesidir.

Protokolü uygulayan nesne, `Symbol.iterator` yerine `Symbol.asyncIterator` adlı yöntemi
tanımlar.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
  E5: { gecikme: 5, hata: "bağlantı yok" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

const olcumKaynagi = {
  istasyonlar: ["A1", "B2"],
  [Symbol.asyncIterator]() {
    const istasyonlar = this.istasyonlar;
    let dizin = 0;
    return {
      async next() {
        if (dizin >= istasyonlar.length) return { value: undefined, done: true };
        const olcum = await olcumGetir(istasyonlar[dizin]);
        dizin += 1;
        return { value: olcum, done: false };
      },
    };
  },
};

for await (const olcum of olcumKaynagi) {
  console.log("protokolden gelen:", olcum.istasyon, olcum.deger);
}
```

```
protokolden gelen: A1 21.4
protokolden gelen: B2 19.8
```

`for await` döngüsü her turda `next` çağırır, dönen sözü bekler ve `done` alanı doğru
olana kadar sürdürür. Döngünün gövdesi de eşzamansız olabilir; her tur bir öncekinin
tamamlanmasını bekler.

#### Eşzamansız Üreteçler

Protokolü elle yazmak, durum değişkenlerini elle taşımak demektir. Eşzamansız üreteç bu
işi devralır: `async function*` biçiminde tanımlanır, gövdesinde hem `await` hem `yield`
kullanılabilir.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
  E5: { gecikme: 5, hata: "bağlantı yok" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

async function* olcumAkisi(istasyonlar) {
  for (const istasyon of istasyonlar) {
    console.log("üretici: istek başlatıldı —", istasyon);
    yield await olcumGetir(istasyon);
  }
}

for await (const olcum of olcumAkisi(["A1", "B2", "C3"])) {
  console.log("tüketici: işlendi —", olcum.istasyon, olcum.deger);
}
console.log("akış bitti");
```

```
üretici: istek başlatıldı — A1
tüketici: işlendi — A1 21.4
üretici: istek başlatıldı — B2
tüketici: işlendi — B2 19.8
üretici: istek başlatıldı — C3
tüketici: işlendi — C3 23.1
akış bitti
```

Çıktıdaki sıra, bu yapının en önemli özelliğini gösterir: üretici, tüketici bir öğeyi
işlemeden bir sonrakini istemiyor. Üreteç `yield` noktasında askıya alınır ve yalnız yeni
bir `next` çağrısı geldiğinde devam eder. Buna **çekmeli (pull-based)** akış denir; hızı
tüketici belirler.

#### Hata ve Erken Çıkış

Akış içindeki bir reddetme, `for await` döngüsünün bulunduğu yerde atılır ve sıradan bir
`try`/`catch` ile yakalanır. Yakalanan hata döngüyü sonlandırır.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  C3: { gecikme: 20, deger: 23.1 },
  D4: { gecikme: 15, hata: "sensör arızası" },
};

function olcumGetir(istasyon) {
  return new Promise((karar, red) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`));
      else karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

async function* olcumAkisi(istasyonlar) {
  for (const istasyon of istasyonlar) {
    yield await olcumGetir(istasyon);
  }
}

try {
  for await (const olcum of olcumAkisi(["A1", "D4", "C3"])) {
    console.log("tüketici:", olcum.istasyon, olcum.deger);
  }
} catch (hata) {
  console.log("akış hatayla durdu:", hata.message);
}
```

```
tüketici: A1 21.4
akış hatayla durdu: D4: sensör arızası
```

Üçüncü istasyona hiç sıra gelmedi: üreteç, hata atılan noktada sonlandı. Akışın hataya
rağmen sürmesi isteniyorsa, hata üretecin **içinde** yakalanıp bir sonuç nesnesine
dönüştürülmelidir — önceki dersteki `allSettled` yaklaşımının akış karşılığı budur.

Döngüden `break` ile çıkmak da üreteci sonlandırır. Bu, açılan kaynakların kapatılması
için bir fırsat sağlar: üretecin `finally` bloğu her iki durumda da çalışır.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon) {
  return new Promise((karar) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => karar({ istasyon, deger: kayit.deger }), kayit.gecikme);
  });
}

async function* olcumAkisi(istasyonlar) {
  try {
    for (const istasyon of istasyonlar) {
      yield await olcumGetir(istasyon);
    }
  } finally {
    console.log("üretici: temizlik çalıştı");
  }
}

for await (const olcum of olcumAkisi(["A1", "B2", "C3"])) {
  console.log("tüketici:", olcum.istasyon);
  if (olcum.istasyon === "B2") break;
}
console.log("döngüden çıkıldı");
```

```
tüketici: A1
tüketici: B2
üretici: temizlik çalıştı
döngüden çıkıldı
```

Temizlik, döngüden çıkılmadan önce çalıştı. Sürekli bir kaynağı — açık bir bağlantıyı,
kurulmuş bir zamanlayıcıyı — okuyan üreteçlerde bu blok zorunludur; aksi halde tüketici
vazgeçse bile kaynak açık kalır.

#### Söz Dizisi Üzerinde `for await`

`for await`, eşzamansız yineleyici olmayan sıradan bir dizi üzerinde de çalışır: her
elemanı `await` ile bekler. Bu, bir söz dizisini sırayla tüketmenin kısa yoludur.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon) {
  return new Promise((karar) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => karar({ istasyon, deger: kayit.deger }), kayit.gecikme);
  });
}

const sozler = ["A1", "B2", "C3"].map((istasyon) => olcumGetir(istasyon));
for await (const olcum of sozler) {
  console.log("sırayla tüketildi:", olcum.istasyon, olcum.deger);
}
```

```
sırayla tüketildi: A1 21.4
sırayla tüketildi: B2 19.8
sırayla tüketildi: C3 23.1
```

Buradaki ayrım önemlidir: istekler **birlikte** başladı, sonuçlar **sırayla** tüketildi.
Sonuçlar dizideki sırayla işlenir; A1 en yavaş olduğu halde ilk sırada beklenir. Bu,
işleme sırasının önemli olduğu durumlarda istenen davranıştır.

#### Geri Basınç

Veri Yapıları kursunda kuyruklar anlatılırken tanıtılan **geri basınç (backpressure)**
kavramı, eşzamansız akışların merkezindedir: üretici tüketiciden hızlıysa, aradaki fark
bir yerde birikmek zorundadır.

İtmeli (push-based) bir kaynakta üretici tüketiciyi beklemez; öğeler bir tampona yığılır.

```js
const tampon = [];
let uretilen = 0;

const kimlik = setInterval(() => {
  uretilen += 1;
  tampon.push({ sira: uretilen });
  if (uretilen === 6) clearInterval(kimlik);
}, 5);

await new Promise((karar) => setTimeout(karar, 45));
console.log("üretilen:", uretilen, "— tampondaki bekleyen:", tampon.length);
```

```
üretilen: 6 — tampondaki bekleyen: 6
```

Tüketici hiç okumadığı için altı öğenin altısı da bellekte bekliyor. Üretim ucu açık
olsaydı tampon sınırsız büyür ve bellek tükenirdi. Bu, bellek konusunda ele alınacak
sızıntı biçimlerinden biridir.

Çekmeli akışta bu sorun tanım gereği yoktur: üreteç bir sonraki değeri ancak istendiğinde
üretir, bu yüzden birikecek bir şey olmaz. İtmeli bir kaynağı çekmeli bir arayüzün
arkasına almak — yani tamponu sınırlamak ve dolduğunda üretimi durdurmak — akış
tasarımının temel kalıbıdır.

#### Özet

- Eşzamansız yineleyici protokolü, `next` çağrısının sonucu bir sözle döndürmesi dışında
  sıradan yineleme protokolüyle aynıdır; nesne `Symbol.asyncIterator` tanımlar.
- `async function*` biçimindeki üreteçler, protokolün durum yönetimini devralır ve
  gövdede hem `await` hem `yield` kullanılmasına izin verir.
- `for await` her turda bir sonraki değeri bekler; akıştaki reddetme döngünün bulunduğu
  yerde atılır ve döngüyü sonlandırır.
- Erken çıkışta üretecin `finally` bloğu çalışır; sürekli kaynakların temizliği buraya
  yazılır.
- Çekmeli akışta hızı tüketici belirler ve geri basınç kendiliğinden oluşur; itmeli
  akışta tampon sınırlanmazsa bellek tükenir.

#### Sonraki Adım

Bu konuda kullanılan ölçüm kaynağı hep aynı süreçte, zamanlayıcıyla taklit edildi. Sonraki
konu kaynağı süreç dışına taşır: ağ üzerinden istek ve yanıt. Orada istek ile yanıt
nesnelerinin yapısı, gövdenin akış halinde okunması ve bu dersteki eşzamansız yinelemenin
ağ gövdelerine doğrudan uygulanışı ele alınacak.

## Ağ ve Veri Erişimi

İstek ve yanıt nesneleri, akış halindeki gövdeler, iptal işaretiyle istek yaşam süresinin denetimi, zaman aşımı ve geçici hatalarda yeniden deneme kalıpları.

### HTTP İstekleri

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/http-istekleri

İstek ve yanıt nesnelerinin yapısı, durum kodunun hata sayılmaması, gövdenin tek kez okunabilmesi ve akış halindeki gövdenin eşzamansız yinelemeyle parça parça işlenmesi.

Buraya kadar ölçüm kaynağı hep aynı süreçte, zamanlayıcıyla taklit edildi. Gerçek bir
kaynak süreç dışındadır: istek ağ üzerinden gider, yanıt parça parça döner ve arada
kontrol edilemeyen bir gecikme vardır.

Bu ders kaynağı süreç dışına taşır. İnternet Nasıl Çalışır kursunda protokol düzeyinde
anlatılan istek–yanıt yapısının JavaScript'teki karşılığı ele alınacak; ölçüm akışı da bir
HTTP hizmetinden okunur hale gelecek.

Örneklerde kullanılan sunucu, aynı programın içinde geri döngü adresinde başlatılır.
Bağlantı noktası olarak sıfır verilir; ana ortam boş bir bağlantı noktası seçer ve seçilen
değer değişkendir, bu yüzden hiçbir yerde yazdırılmaz.

#### İstek ve Yanıt Birer Nesnedir

İstek ve yanıt, ağ katmanının altında kalan ayrıntılardan bağımsız iki nesneyle temsil
edilir. İkisi de ağ olmadan da oluşturulabilir; bu, yapılarının incelenmesini kolaylaştırır.

```js
const istek = new Request("http://ornek.gecersiz/olcum/B2", {
  method: "POST",
  headers: { "content-type": "application/json", "x-istemci": "olcum-toplayici" },
  body: JSON.stringify({ istasyon: "B2", deger: 19.8 }),
});

console.log("yöntem:", istek.method);
console.log("yol:", new URL(istek.url).pathname);
console.log("başlık:", istek.headers.get("x-istemci"));
console.log("gövde:", await istek.json());

const yanit = new Response(JSON.stringify({ durum: "kaydedildi" }), {
  status: 201,
  headers: { "content-type": "application/json" },
});

console.log("yanıt durumu:", yanit.status, yanit.ok);
console.log("yanıt gövdesi:", await yanit.json());
```

```
yöntem: POST
yol: /olcum/B2
başlık: olcum-toplayici
gövde: { istasyon: 'B2', deger: 19.8 }
yanıt durumu: 201 true
yanıt gövdesi: { durum: 'kaydedildi' }
```

Üç nokta dikkat çeker. Başlıklar sıradan bir nesne değil, ad–değer erişimi sunan ayrı bir
yapıdır. Gövdeyi okuyan yöntemler (`json`, `text`) söz döndürür; çünkü gövde henüz tam
olarak gelmemiş olabilir. Durum kodu ile `ok` alanı birlikte taşınır: `ok`, durum kodunun
başarı aralığında olup olmadığını söyler.

#### Ağ Hatası ile Başarısız Durum Kodu Ayrıdır

İstek gönderen fonksiyon, yalnız isteği **iletemediğinde** reddeder: bağlantı kurulamadı,
ad çözümlenemedi, bağlantı koptu. Sunucu bir yanıt döndürdüyse — durum kodu ne olursa
olsun — dönen söz yerine gelir.

```js
import { createServer } from "node:http";

const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

const sunucu = createServer((istek, yanit) => {
  const yol = new URL(istek.url, "http://yerel").pathname;
  const istasyon = yol.replace("/olcum/", "");
  const kayit = KAYNAK[istasyon];

  if (kayit === undefined) {
    yanit.writeHead(404, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ hata: "bilinmeyen istasyon", istasyon }));
    return;
  }

  setTimeout(() => {
    yanit.writeHead(200, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ istasyon, deger: kayit.deger }));
  }, kayit.gecikme);
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

const yanit = await fetch(`${taban}/olcum/B2`);
console.log("durum:", yanit.status, "başarılı mı:", yanit.ok);
console.log("içerik türü:", yanit.headers.get("content-type"));
console.log("gövde:", await yanit.json());

const bulunamadi = await fetch(`${taban}/olcum/Z9`);
console.log("durum:", bulunamadi.status, "başarılı mı:", bulunamadi.ok);
console.log("gövde:", await bulunamadi.json());

sunucu.close();
```

```
durum: 200 başarılı mı: true
içerik türü: application/json
gövde: { istasyon: 'B2', deger: 19.8 }
durum: 404 başarılı mı: false
gövde: { hata: 'bilinmeyen istasyon', istasyon: 'Z9' }
```

Bilinmeyen istasyon isteği bir hata **atmadı**; 404 durum kodlu geçerli bir yanıt döndü.
Bu, sık yapılan bir hatanın kaynağıdır: `try`/`catch` bloğu tek başına başarısız istekleri
yakalamaz.

Doğru kalıp, durum kodunu açıkça denetleyip başarısız yanıtı hataya çevirmektir. Denetim
ayrı bir işleve alındığında her çağrı noktasında tekrarlanması gerekmez:

```js
function denetle(yanit) {
  if (!yanit.ok) throw new Error(`istek başarısız: ${yanit.status}`);
  return yanit;
}

const basarili = new Response("veri", { status: 200 });
const basarisiz = new Response("bulunamadı", { status: 404 });

console.log("başarılı yanıt geçti:", denetle(basarili).status);

try {
  denetle(basarisiz);
} catch (hata) {
  console.log("denetim yakaladı:", hata.message);
}
```

```
başarılı yanıt geçti: 200
denetim yakaladı: istek başarısız: 404
```

Ayrım anlamlıdır: ağ hatası "isteğin sunucuya ulaşıp ulaşmadığı bilinmiyor" demektir;
başarısız durum kodu ise "ulaştı, sunucu değerlendirdi ve reddetti" demektir. İkisi farklı
kurtarma stratejileri gerektirir — bu ayrım yeniden deneme dersinde belirleyici olacak.

#### Gövde Bir Kez Okunur

Yanıt gövdesi bir akıştır; okunduğunda tükenir. İkinci okuma girişimi hata üretir.

```js
const yanit = new Response(JSON.stringify({ istasyon: "B2", deger: 19.8 }), {
  headers: { "content-type": "application/json" },
});

console.log("gövde kullanıldı mı:", yanit.bodyUsed);
console.log("birinci okuma:", await yanit.json());
console.log("gövde kullanıldı mı:", yanit.bodyUsed);

try {
  await yanit.json();
} catch (hata) {
  console.log("ikinci okuma hatası:", hata.constructor.name);
}
```

```
gövde kullanıldı mı: false
birinci okuma: { istasyon: 'B2', deger: 19.8 }
gövde kullanıldı mı: true
ikinci okuma hatası: TypeError
```

Bu kısıt, gövdenin bellekte tutulmadığının doğrudan sonucudur. Aynı gövdeye iki kez
bakılması gerekiyorsa iki yol vardır: sonucu bir değişkende saklamak ya da yanıtı
`clone` ile çoğaltmak. Çoğaltma, iki okuyucunun aynı veriyi ayrı ayrı tüketmesi demektir
ve bellekte kopya tutar; bu nedenle varsayılan seçim sonucu saklamaktır.

#### Gövdeyi Akış Halinde İşlemek

Gövdenin tamamını beklemek, büyük yanıtlarda hem gecikme hem bellek maliyeti doğurur.
Gövde, eşzamansız bir yineleyicidir: önceki dersteki `for await` döngüsü doğrudan
uygulanır.

```js
import { createServer } from "node:http";

const sunucu = createServer((istek, yanit) => {
  yanit.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
  const satirlar = ["A1;21.4", "B2;19.8", "C3;23.1"];
  let dizin = 0;

  function sonrakiSatir() {
    if (dizin === satirlar.length) {
      yanit.end();
      return;
    }
    yanit.write(`${satirlar[dizin]}\n`);
    dizin += 1;
    setTimeout(sonrakiSatir, 20);
  }
  sonrakiSatir();
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

const yanit = await fetch(`${taban}/akis`);
const cozucu = new TextDecoder();
let parcaSayisi = 0;

for await (const parca of yanit.body) {
  parcaSayisi += 1;
  console.log("parça", parcaSayisi, "—", cozucu.decode(parca, { stream: true }).trim());
}
console.log("toplam parça:", parcaSayisi);

sunucu.close();
```

```
parça 1 — A1;21.4
parça 2 — B2;19.8
parça 3 — C3;23.1
toplam parça: 3
```

Sunucu üç satırı yirmişer milisaniye arayla yazdı; istemci her satırı geldiği anda işledi,
son satırı beklemedi. Ölçüm akışı böylece gerçek anlamda akış haline geldi: veri
tamamlanmadan işlenmeye başlanabiliyor.

İki ayrıntı taşınabilirlik için önemlidir. Birincisi, parça sınırları **veri sınırlarıyla
aynı değildir**; ağ, satırları böler ya da birleştirebilir. Bu örnekte her yazma arasına
gecikme konduğu için parçalar satırlarla örtüştü, ama genel durumda örtüşmez. Doğru
uygulama, gelen parçaları bir tampona ekleyip satır sonu arayan bir çözümleyicidir.

İkincisi, çözücüye verilen `stream` seçeneği, çok baytlı bir karakterin iki parçaya
bölünmesi durumunu doğru ele alır. Bilgisayarlar Nasıl Çalışır kursundaki karakter
kodlaması konusunun doğrudan pratik karşılığıdır: bayt sınırı ile karakter sınırı aynı
şey değildir.

#### Kalan Boşluk

Bu dersin programlarında bir varsayım gizliydi: sunucu er ya da geç yanıt verir. Vermezse
`await` sonsuza kadar bekler; ne sonuç gelir ne hata. Söz konusu boşluk söz kavramı
dersinde işaret edilmişti ve hâlâ açık.

Aynı biçimde, başlatılan bir isteğin sonucundan vazgeçmenin de bir yolu yok: birleştirici
dersinde görüldüğü gibi, beklenmemek iptal edilmek değildir. İstek sürer, yanıt gelir,
bellek tutulur.

#### Özet

- İstek ve yanıt birer nesnedir; başlıklar ad–değer erişimi sunan ayrı bir yapıda tutulur
  ve gövde okuma yöntemleri söz döndürür.
- İstek gönderen fonksiyon yalnız iletim başarısızlığında reddeder; başarısız durum kodu
  geçerli bir yanıttır ve `ok` alanıyla denetlenmelidir.
- Gövde bir akıştır ve bir kez okunur; ikinci okuma hata verir.
- Gövde eşzamansız yineleyicidir; `for await` ile parça parça işlenebilir.
- Parça sınırları veri sınırlarıyla örtüşmez; çözümleme tamponla yapılmalıdır.

#### Sonraki Adım

Sonraki ders açık kalan iki boşluğu birlikte kapatır: bir isteğe süre sınırı koymak ve
başlatılmış bir isteği gerçekten durdurmak. İptal işareti kavramı tanıtılacak, zaman aşımı
bu işaret üzerine kurulacak ve yalnız beklemeyi kesen yarış tabanlı çözümün neden yetersiz
kaldığı gösterilecek.

### İptal ve Zaman Aşımı

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/iptal-ve-zaman-asimi

İptal işareti kavramı, isteğin gerçekten durdurulması, zaman aşımının işaret üzerine kurulması, yarış tabanlı zaman aşımının neden yetersiz kaldığı ve işaretlerin birleştirilmesi.

Önceki ders iki boşlukla kapandı: hiç yanıt vermeyen bir kaynak sonsuza kadar beklenir ve
başlatılmış bir istekten vazgeçmenin yolu yoktur. İki boşluk da aynı eksikten doğar —
eşzamansız bir işlemi *dışarıdan* etkileyecek bir kanal yoktur.

Bu ders o kanalı kurar. Kanalın adı **iptal işareti (abort signal)**; zaman aşımı da
bunun üzerine kurulan özel bir durumdur. Ölçüm akışı burada durdurulabilir hale gelir.

#### İptal İşareti

İptal düzeneği iki nesneden oluşur. **Denetleyici (controller)** iptali başlatan taraftadır
ve bir `abort` yöntemi taşır. **İşaret (signal)**, işi yapan tarafa verilir; iptal edilip
edilmediğini bildirir ve iptal anında bir olay yayar.

Ayrım önemlidir: işareti alan taraf iptal *edemez*, yalnız iptali *gözler*. Böylece iptal
yetkisi çağıranda kalır.

```js
import { createServer } from "node:http";

const sunucu = createServer((istek, yanit) => {
  const kimlik = setTimeout(() => {
    yanit.writeHead(200, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ istasyon: "A1", deger: 21.4 }));
  }, 200);
  yanit.on("close", () => clearTimeout(kimlik));
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

const denetleyici = new AbortController();
setTimeout(() => denetleyici.abort(), 20);

try {
  await fetch(`${taban}/olcum/A1`, { signal: denetleyici.signal });
  console.log("bu satır çalışmaz");
} catch (hata) {
  console.log("istek durduruldu:", hata.name);
}

try {
  await fetch(`${taban}/olcum/A1`, { signal: AbortSignal.timeout(20) });
} catch (hata) {
  console.log("zaman aşımı:", hata.name);
}

sunucu.close();
```

```
istek durduruldu: AbortError
zaman aşımı: TimeoutError
```

Sunucu iki yüz milisaniye sonra yanıt verecek biçimde kuruldu; iki istek de yirminci
milisaniyede sonlandı. Hata adları iki durumu ayırt eder: elle iptal `AbortError`, süre
sınırı `TimeoutError` üretir. Ayrım, kurtarma kararında kullanılır — kullanıcı vazgeçtiyse
yeniden denemek anlamsızdır; süre dolduysa anlamlı olabilir.

`AbortSignal.timeout` bir kısayoldur: belirtilen süre sonunda kendiliğinden iptal olan bir
işaret üretir. Zamanlayıcıyı elle kurmaya ve iptal etmeye gerek bırakmaz.

Sunucu tarafındaki `close` dinleyicisi de aynı düşüncenin karşılığıdır: istemci bağlantıyı
kestiğinde sunucu da bekleyen işini iptal eder. İptal, tek yönlü bir çağrı değil, iki uçta
da uygulanması gereken bir sözleşmedir.

#### Kendi İşlevinize İptal Eklemek

İptal, ana ortamın ayrıcalığı değildir. Söz döndüren her işlev, seçenek nesnesinde bir
işaret kabul ederek aynı sözleşmeye katılabilir. Kurallar üçtür: işaret zaten iptal
edilmişse hemen reddet, iptal olayında kaynakları serbest bırak ve reddet, dinleyiciyi
bir kez çalışacak biçimde kaydet.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
};

function olcumGetir(istasyon, secenekler = {}) {
  const isaret = secenekler.signal;
  return new Promise((karar, red) => {
    if (isaret?.aborted) {
      red(isaret.reason);
      return;
    }
    const kayit = KAYNAK[istasyon];
    const kimlik = setTimeout(() => {
      console.log("kaynak: iş tamamlandı —", istasyon);
      karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);

    isaret?.addEventListener(
      "abort",
      () => {
        clearTimeout(kimlik);
        console.log("kaynak: iş durduruldu —", istasyon);
        red(isaret.reason);
      },
      { once: true },
    );
  });
}

const denetleyici = new AbortController();
setTimeout(() => denetleyici.abort(new Error("toplama iptal edildi")), 15);

try {
  await olcumGetir("A1", { signal: denetleyici.signal });
} catch (hata) {
  console.log("çağıran gördü:", hata.message);
}

await new Promise((karar) => setTimeout(karar, 40));
console.log("bekleme bitti; A1 için başka çıktı olmadı");
```

```
kaynak: iş durduruldu — A1
çağıran gördü: toplama iptal edildi
bekleme bitti; A1 için başka çıktı olmadı
```

Son satır, iptalin gerçekten iş durdurduğunun kanıtıdır: kırk milisaniye daha beklendiği
halde "iş tamamlandı" iletisi hiç yazılmadı, çünkü zamanlayıcı temizlendi.

`abort` yöntemine verilen değer, işaretin `reason` alanına yerleşir ve reddetme nedeni
olur. Verilmezse ana ortam `AbortError` adlı bir hata üretir. Kendi hatanızı vermek,
iptal nedenini çağrı yığıtı olmadan taşımanın en doğrudan yoludur.

#### Yarış Tabanlı Zaman Aşımı Neden Yetersiz

Zaman aşımı, birleştiriciler bilindiğinde ilk akla gelen biçimde de yazılabilir: işi bir
zamanlayıcıyla yarıştırmak. Bu çözüm beklemeyi keser, ama işi kesmez.

```js
const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
};

function olcumGetir(istasyon) {
  return new Promise((karar) => {
    const kayit = KAYNAK[istasyon];
    setTimeout(() => {
      console.log("kaynak: iş yine de tamamlandı —", istasyon);
      karar({ istasyon, deger: kayit.deger });
    }, kayit.gecikme);
  });
}

function zamanAsimi(sure) {
  return new Promise((karar, red) => {
    setTimeout(() => red(new Error(`zaman aşımı: ${sure} ms`)), sure);
  });
}

try {
  await Promise.race([olcumGetir("A1"), zamanAsimi(10)]);
} catch (hata) {
  console.log("çağıran gördü:", hata.message);
}

await new Promise((karar) => setTimeout(karar, 40));
console.log("bekleme bitti");
```

```
çağıran gördü: zaman aşımı: 10 ms
kaynak: iş yine de tamamlandı — A1
bekleme bitti
```

Çağıran onuncu milisaniyede hata aldı; iş otuzuncu milisaniyede tamamlandı. Yani sunucuya
gönderilen istek sürdü, yanıt geldi, bellek ve bağlantı tutuldu — yalnız sonucu kimse
kullanmadı.

Sonuçları üç başlıkta toplanır: kaynak boşa harcanır, sonuçlanmamış söz ve kapattığı
değerler bellekte kalır ve yan etkili bir işlemde — örneğin bir kaydın yazılmasında —
"zaman aşımına uğradı" denen işlem aslında tamamlanmış olabilir.

Kural şudur: **yarış beklemeyi sonlandırır, iptal işi sonlandırır.** İkisi birbirinin
yerine geçmez; doğru zaman aşımı, süre dolduğunda iptali tetikleyendir.

#### İşaretleri Birleştirmek

Bir işlemin birden çok iptal nedeni olabilir: kullanıcı vazgeçti, süre doldu, üst düzey
işlem iptal edildi. `AbortSignal.any`, birden çok işareti tek işarette toplar; hangisi
önce iptal olursa birleşik işaret onun nedeniyle iptal olur.

```js
const kullanici = new AbortController();
const birlesik = AbortSignal.any([kullanici.signal, AbortSignal.timeout(30)]);

birlesik.addEventListener("abort", () => {
  console.log("birleşik işaret durduruldu; neden:", birlesik.reason.name);
});

setTimeout(() => kullanici.abort(), 10);

await new Promise((karar) => setTimeout(karar, 50));
console.log("kullanıcı işareti durduruldu mu:", kullanici.signal.aborted);
```

```
birleşik işaret durduruldu; neden: AbortError
kullanıcı işareti durduruldu mu: true
```

Kullanıcı iptali onuncu milisaniyede geldiği için birleşik işaret onunla sonuçlandı; otuz
milisaniyelik süre sınırı hiç devreye girmedi. Bu kalıp, iptali çağrı ağacı boyunca
taşımayı mümkün kılar: üst düzey bir işlemin işareti, alt işlemlerin kendi süre
sınırlarıyla birleştirilerek aşağı aktarılır.

#### İptalin Sınırları

İki sınır açıkça bilinmelidir.

İptal **işbirliğine dayanır.** İşareti gözlemeyen bir işlev iptal edilemez; iptal isteği
yalnız işareti dinleyenlere ulaşır. Bu yüzden söz döndüren her işlev, uzun sürebilecek
işlerde işaret parametresini kabul etmelidir.

İptal **geri alma değildir.** Sunucuya ulaşmış bir isteğin sunucudaki etkisi iptalle
geri alınmaz; yalnız yanıtın beklenmesinden vazgeçilir. Yan etkili işlemlerde bu ayrım,
sonraki dersteki yeniden deneme kararının temelini oluşturur.

#### Özet

- İptal, denetleyici ve işaret olmak üzere iki nesneyle kurulur; işareti alan taraf iptali
  yalnız gözler.
- Söz döndüren kendi işlevleriniz, işareti kabul edip iptal olayında kaynaklarını serbest
  bırakarak aynı sözleşmeye katılabilir.
- `AbortSignal.timeout` süre sınırını işaret olarak üretir; elle iptal `AbortError`, süre
  sınırı `TimeoutError` verir.
- Yarış tabanlı zaman aşımı yalnız beklemeyi keser; iş sürer, kaynak tutulur, yan etki
  gerçekleşebilir.
- `AbortSignal.any` birden çok iptal nedenini tek işarette toplar ve iptalin çağrı ağacı
  boyunca aktarılmasını sağlar.

#### Sonraki Adım

İptal ve zaman aşımı, başarısızlığı belirlenmiş hale getirdi: artık bir isteğin ne zaman
biteceği biliniyor. Geriye başarısızlıktan sonra ne yapılacağı kalıyor. Sonraki ders geçici
hataları kalıcı olanlardan ayırır, hangi işlemlerin güvenle yeniden denenebileceğini
belirler, bekleme sürelerinin neden artırılarak seçildiğini ve deneme bütçesinin neden
sınırlı tutulduğunu gösterir. Ölçüm akışı böylece hem durdurulabilir hem de dayanıklı hale
gelecek.

### Hata ve Yeniden Deneme Kalıpları

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/hata-ve-yeniden-deneme-kaliplari

Geçici ve kalıcı hataların ayrımı, artan bekleme süreleriyle yeniden deneme, deneme bütçesi, işlem anahtarıyla yinelemeye dayanıklılık ve toplu başarısızlığın önlenmesi.

İptal ve zaman aşımı, başarısızlığı belirlenmiş hale getirdi: bir isteğin ne zaman
biteceği artık biliniyor. Geriye başarısızlıktan sonra ne yapılacağı kalıyor.

Her başarısızlık aynı değildir. Bazıları kendiliğinden geçer — kısa süreli aşırı yük, tek
bir paketin kaybı. Bazıları tekrarlandığında aynı sonucu verir — yanlış adres, bilinmeyen
istasyon. Bu ders ayrımı kurar, yeniden denemenin doğru biçimini yazar ve hangi işlemlerin
güvenle tekrarlanabileceğini belirler.

#### Geçici ve Kalıcı Hata

Yeniden denemenin tek gerekçesi vardır: **aynı isteğin bu kez başarılı olma olasılığı.**
Bu olasılık yoksa deneme, hem çağıranı bekletir hem sunucuyu meşgul eder.

| Hata | Sınıf | Gerekçe |
|---|---|---|
| Bağlantı kurulamadı | Geçici | Ağ koşulu değişebilir |
| 408, 429 | Geçici | Süre veya hız sınırı; sonra denenebilir |
| 500, 502, 503, 504 | Geçici | Sunucu tarafında geçici durum |
| 400, 401, 403, 404 | Kalıcı | İstek yeniden gönderilse de aynı sonuç |

Ayrım biçimsel değil anlamsaldır: 404, "bu kaynak yok" der; yeniden sormak yanıtı
değiştirmez. 503 ise "şu an hizmet veremiyorum" der; bu, zamana bağlı bir ifadedir.

Ağ hatası ile başarısız durum kodu arasındaki ayrım da burada belirleyici olur. Önceki
derste gösterildiği gibi, ağ hatası isteğin sunucuya ulaşıp ulaşmadığını bilmediğimiz
durumdur. Bu belirsizlik, yan etkili işlemlerde yeniden denemeyi tehlikeli kılar.

#### Artan Bekleme ile Yeniden Deneme

Başarısız bir isteği hemen tekrarlamak, sorunu ağırlaştırır: aşırı yüklü bir sunucu, aynı
anda vazgeçen bütün istemcilerin tekrar denemesiyle karşılaşır. Bu nedenle bekleme süresi
her denemede artırılır — **üstel geri çekilme (exponential backoff)**.

```js
import { createServer } from "node:http";

let istekSayisi = 0;

const sunucu = createServer((istek, yanit) => {
  istekSayisi += 1;
  if (istekSayisi <= 2) {
    yanit.writeHead(503, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ hata: "hizmet geçici olarak kullanılamıyor" }));
    return;
  }
  yanit.writeHead(200, { "content-type": "application/json" });
  yanit.end(JSON.stringify({ istasyon: "A1", deger: 21.4 }));
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

const GECICI_DURUMLAR = new Set([408, 429, 500, 502, 503, 504]);

function bekle(sure) {
  return new Promise((karar) => setTimeout(karar, sure));
}

async function denemeliGetir(adres, denemeSayisi, tabanBekleme) {
  let sonHata = null;

  for (let deneme = 1; deneme <= denemeSayisi; deneme += 1) {
    const yanit = await fetch(adres);
    if (yanit.ok) {
      console.log("deneme", deneme, "— başarılı");
      return await yanit.json();
    }
    if (!GECICI_DURUMLAR.has(yanit.status)) {
      throw new Error(`kalıcı hata: ${yanit.status}`);
    }
    sonHata = new Error(`geçici hata: ${yanit.status}`);
    console.log("deneme", deneme, "— geçici hata:", yanit.status);

    if (deneme < denemeSayisi) {
      const bekleme = tabanBekleme * 2 ** (deneme - 1);
      console.log("  planlanan bekleme:", bekleme, "ms");
      await bekle(bekleme);
    }
  }

  throw new Error(`deneme bütçesi tükendi: ${denemeSayisi}`, { cause: sonHata });
}

console.log("sonuç:", await denemeliGetir(`${taban}/olcum/A1`, 4, 10));

sunucu.close();
```

```
deneme 1 — geçici hata: 503
  planlanan bekleme: 10 ms
deneme 2 — geçici hata: 503
  planlanan bekleme: 20 ms
deneme 3 — başarılı
sonuç: { istasyon: 'A1', deger: 21.4 }
```

Sunucu ilk iki isteği geri çevirecek biçimde kuruldu; üçüncü deneme başarılı oldu.
Yazdırılan bekleme değerleri ölçüm değil, hesaplanmış plandır: taban değer her denemede
ikiyle çarpılır.

Gerçek dağıtımlarda bu plana bir de rastgele sapma eklenir. Gerekçesi şudur: aynı anda
başarısız olan istemciler aynı planı uygularsa, yeniden denemeleri de aynı ana denk gelir
ve sunucu ikinci bir dalgayla karşılaşır. Sapma, dalgayı zamana yayar. Bu derste
çıktıların belirlenimci kalması için sapma kullanılmadı.

#### Deneme Bütçesi ve Kalıcı Hatada Erken Çıkış

İki koruma zorunludur. Deneme sayısı sınırlanmalıdır, yoksa hizmet dönmediğinde çağıran
sonsuza kadar bekler. Kalıcı hatada ise hiç beklenmemelidir.

```js
import { createServer } from "node:http";

const sunucu = createServer((istek, yanit) => {
  const yol = new URL(istek.url, "http://yerel").pathname;
  if (yol === "/olcum/Z9") {
    yanit.writeHead(404, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ hata: "bilinmeyen istasyon" }));
    return;
  }
  yanit.writeHead(503, { "content-type": "application/json" });
  yanit.end(JSON.stringify({ hata: "hizmet geçici olarak kullanılamıyor" }));
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

const GECICI_DURUMLAR = new Set([408, 429, 500, 502, 503, 504]);

function bekle(sure) {
  return new Promise((karar) => setTimeout(karar, sure));
}

async function denemeliGetir(adres, denemeSayisi, tabanBekleme) {
  let sonHata = null;

  for (let deneme = 1; deneme <= denemeSayisi; deneme += 1) {
    const yanit = await fetch(adres);
    if (yanit.ok) return await yanit.json();
    if (!GECICI_DURUMLAR.has(yanit.status)) {
      throw new Error(`kalıcı hata: ${yanit.status}`);
    }
    sonHata = new Error(`geçici hata: ${yanit.status}`);
    console.log("deneme", deneme, "— geçici hata:", yanit.status);
    if (deneme < denemeSayisi) await bekle(tabanBekleme * 2 ** (deneme - 1));
  }

  throw new Error(`deneme bütçesi tükendi: ${denemeSayisi}`, { cause: sonHata });
}

try {
  await denemeliGetir(`${taban}/olcum/Z9`, 4, 10);
} catch (hata) {
  console.log("kalıcı hatada hiç beklenmedi:", hata.message);
}

try {
  await denemeliGetir(`${taban}/olcum/A1`, 3, 10);
} catch (hata) {
  console.log("son hata:", hata.message);
  console.log("kök neden:", hata.cause.message);
}

sunucu.close();
```

```
kalıcı hatada hiç beklenmedi: kalıcı hata: 404
deneme 1 — geçici hata: 503
deneme 2 — geçici hata: 503
deneme 3 — geçici hata: 503
son hata: deneme bütçesi tükendi: 3
kök neden: geçici hata: 503
```

Bilinmeyen istasyon için tek istek yapıldı; geçici hata veren adres için üç deneme
tüketildi ve bütçe dolduğunda hata, kök nedeni taşıyarak yukarı verildi. Hata yönetimi
dersindeki `cause` kalıbı burada tanı için gereklidir: çağıran hem "kaç denemede
vazgeçildiğini" hem "en son ne olduğunu" görür.

Yeniden denemenin toplam süresi de bir sınırdır. Deneme sayısı ile bekleme planı
birleşince, en kötü durumda geçecek süre baştan hesaplanabilir olmalıdır; iptal işareti bu
toplam süreye de bağlanabilir.

#### Yan Etkili İşlemleri Yeniden Denemek

Ölçüm okumak zararsızdır: aynı isteği iki kez göndermek yalnız fazladan iş yapar. Ölçüm
**yazmak** öyle değildir. Ağ hatası aldığınızda isteğin sunucuya ulaşıp ulaşmadığını
bilemezsiniz; tekrar gönderirseniz aynı kayıt iki kez oluşabilir.

Çözüm, isteğe onu tekil kılan bir **işlem anahtarı** eklemektir. Sunucu, aynı anahtarla
gelen ikinci isteği yeni bir işlem saymaz; ilk işlemin sonucunu döndürür.

```js
import { createServer } from "node:http";

const kayitlar = [];

const sunucu = createServer((istek, yanit) => {
  let govde = "";
  istek.on("data", (parca) => {
    govde += parca;
  });
  istek.on("end", () => {
    const anahtar = istek.headers["x-islem-anahtari"];
    const varOlan = anahtar === undefined ? undefined : kayitlar.find((k) => k.anahtar === anahtar);

    if (varOlan === undefined) {
      kayitlar.push({ anahtar, veri: JSON.parse(govde) });
    }
    yanit.writeHead(200, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ toplamKayit: kayitlar.length }));
  });
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

async function olcumYaz(basliklar) {
  const yanit = await fetch(`${taban}/kayit`, {
    method: "POST",
    headers: { "content-type": "application/json", ...basliklar },
    body: JSON.stringify({ istasyon: "A1", deger: 21.4 }),
  });
  return await yanit.json();
}

console.log("anahtarsız 1. deneme:", await olcumYaz({}));
console.log("anahtarsız 2. deneme:", await olcumYaz({}));
console.log("anahtarlı 1. deneme:", await olcumYaz({ "x-islem-anahtari": "yazma-77" }));
console.log("anahtarlı 2. deneme:", await olcumYaz({ "x-islem-anahtari": "yazma-77" }));

sunucu.close();
```

```
anahtarsız 1. deneme: { toplamKayit: 1 }
anahtarsız 2. deneme: { toplamKayit: 2 }
anahtarlı 1. deneme: { toplamKayit: 3 }
anahtarlı 2. deneme: { toplamKayit: 3 }
```

Anahtarsız iki istek iki kayıt üretti; anahtarlı iki istek tek kayıt üretti. Aynı isteğin
tekrarının sonucu değiştirmemesi özelliğine **yinelemeye dayanıklılık (idempotence)**
denir; İnternet Nasıl Çalışır kursunda aynı özellik HTTP yöntemleri için tanıtılmıştı.
Buradaki eklenti, özelliğin yöntemden gelmediği durumda anahtarla sağlanabilmesidir.

Kural şu biçimde özetlenir: **yeniden deneme, ancak işlem yinelemeye dayanıklıysa
güvenlidir.** Değilse önce dayanıklı hale getirilmeli, sonra denenmelidir.

#### Yeniden Denemenin Sınırı

Yeniden deneme, tek bir isteği kurtarır; sistemi kurtarmaz. Hizmet gerçekten düşmüşse,
bütün istemcilerin denemeye devam etmesi yükü artırır ve toparlanmayı geciktirir.

Bu nedenle uygulamalarda deneme, bir üst düzenekle sınırlandırılır: art arda başarısızlık
belirli bir eşiği geçtiğinde istekler bir süre hiç gönderilmez, sonra tek bir yoklama
isteğiyle durum sınanır. Aynı düşüncenin bir başka biçimi, aynı anda süren toplam deneme
sayısına sınır koymaktır.

Ölçüm akışı açısından sonuç şudur: her istasyon için birkaç deneme yapılır, kalıcı hatada
hemen vazgeçilir, bütçe dolduğunda istasyon "veri yok" olarak işaretlenir ve toplama
sürer. `allSettled` ile kurulan dayanıklı toplama, bu davranışın doğal kabıdır.

#### Özet

- Yeniden deneme yalnız geçici hatalarda anlamlıdır; kalıcı hatada hem çağıran bekletilir
  hem sunucu boşuna meşgul edilir.
- Bekleme süresi her denemede artırılır; sabit aralıklı denemeler aşırı yüklü bir hizmete
  ikinci dalga olarak çarpar.
- Deneme sayısı ve toplam süre sınırlanmalı, bütçe dolduğunda hata kök nedeniyle birlikte
  yukarı verilmelidir.
- Yan etkili işlemler ancak yinelemeye dayanıklıysa yeniden denenebilir; dayanıklılık
  işlem anahtarıyla sağlanır.
- Yeniden deneme tek isteği kurtarır; sistem düzeyinde ayrıca bir sınırlama düzeneği
  gerekir.

#### Sonraki Adım

Ağ konusu tamamlandı: istek yapıldı, sınırlandı, iptal edildi ve dayanıklı hale getirildi.
Bu derslerde birkaç kez "bellekte kalır" denildi ama neyin, neden ve ne kadar süreyle
kaldığı açıklanmadı. Sonraki konu bunu ele alır: belleğin ayrılma ve serbest bırakılma
döngüsü, erişilebilirlik temelli toplama, eşzamansız kodun bellekte tuttuğu şeyler ve
sızıntının araçlarla tanısı.

## Bellek ve Başarım

Belleğin ayrılma ve serbest bırakılma döngüsü, erişilebilirlik temelli çöp toplama, anlık görüntü karşılaştırmasıyla sızıntı tanısı, ana iş parçacığı profillemesi ve hata ayıklama araçları.

### Bellek Yaşam Döngüsü

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/bellek-yasam-dongusu

Ayırma, kullanım ve serbest bırakma aşamaları; erişilebilirliğin kök kümesi; eşzamansız kodda zamanlayıcı, dinleyici ve sonuçlanmamış sözlerin bellekte tuttukları.

Önceki konuda birkaç kez "bellekte kalır" denildi: yarışı kaybeden sözler, iptal edilmeyen
istekler, tüketilmeyen tamponlar. Neyin, neden ve ne kadar süreyle kaldığı ise
açıklanmadı.

Bu konu onu açıklar. Başlangıç noktası, her programlama dilinde ortak olan üç aşamalı
döngüdür; JavaScript'i ayıran, üçüncü aşamanın kime ait olduğudur.

#### Üç Aşama

**Ayırma.** Bir değer için yer ayrılır. JavaScript'te bu, açıkça istenmez; bir nesne,
dizi, dizgi ya da fonksiyon yazıldığında kendiliğinden olur.

**Kullanım.** Ayrılan yer okunur ve yazılır. Programın asıl işi budur.

**Serbest bırakma.** Yer geri verilir. C gibi dillerde bunu program yapar; JavaScript'te
çalışma zamanı yapar.

Üçüncü aşamanın otomatik olması, sorunun ortadan kalktığı anlamına gelmez; sorunu
dönüştürür. Elle yönetilen bellekte hata "erken serbest bırakmak"tır — Bilgisayarlar Nasıl
Çalışır kursundaki sarkan işaretçi. Otomatik yönetimde hata "gereğinden uzun süre
başvurmak"tır. İlkinde program çöker, ikincisinde şişer.

Nesneler, aynı kursta tanıtılan bellek düzenindeki **öbek (heap)** alanında yaşar; yerel
değişkenler ise çağrı yığıtındaki çerçevede tutulur. Çerçeve, fonksiyon döndüğünde
kaybolur; öbekteki nesne ise ona başvuran kimse kalmadığında toplanabilir hale gelir.

#### Erişilebilirlik ve Kökler

Çalışma zamanı, bir nesnenin "gerekli olup olmadığını" bilemez. Bunun yerine ölçülebilir
bir ölçüt kullanır: **erişilebilirlik (reachability)**. Bir kök kümesinden başlayıp
başvuruları izleyerek ulaşılabilen her nesne yaşar; ulaşılamayan her nesne toplanabilir.

Kök kümesi şunlardan oluşur:

- Yürüyen çağrı yığıtındaki yerel değişkenler ve parametreler
- Genel kapsamdaki bağlamalar
- Modül düzeyindeki bağlamalar
- **Bekleyen zamanlayıcıların geri çağrıları**
- **Kayıtlı olay dinleyicileri**
- **Sonuçlanmamış sözlerin geri çağrıları**

Son üçü bu kursun konusudur ve çoğu bellek sorununun kaynağıdır. Bir zamanlayıcı
kurulduğunda, geri çağrısı ve o geri çağrının kapattığı bütün değişkenler bir kök haline
gelir. Kapanış kavramı JavaScript'te Nesneler ve Fonksiyonlar kursunda tanımlanmıştı;
buradaki eklenti, kapanışın **yaşam süresini eşzamansız bir olayın belirlemesidir**.

#### Bir Nesne Ne Zaman Toplanabilir

Kural, çalıştırılabilir bir örnekle gösterilebilir. Aşağıdaki program iki nesne üretir:
biri yalnız bir fonksiyonun içinde kullanılır, diğeri bir zamanlayıcı geri çağrısı
tarafından kapatılır.

Toplama anını gözlemek için iki araç kullanılır. `FinalizationRegistry`, kaydedilen bir
nesne toplandığında bir geri çağrı çalıştırır. Toplamayı elle tetiklemek için de çalışma
zamanı ilgili seçenekle başlatılır; aksi halde toplayıcı kendi zamanlamasına göre çalışır
ve çıktı belirlenimci olmaz.

```js
const kayitDefteri = new FinalizationRegistry((ad) => {
  console.log("toplandı:", ad);
});

function geciciOlcum() {
  const olcum = { istasyon: "A1", deger: 21.4 };
  kayitDefteri.register(olcum, "geçici ölçüm");
  return olcum.deger;
}

function zamanlayicininTuttugu() {
  const olcum = { istasyon: "B2", deger: 19.8 };
  kayitDefteri.register(olcum, "zamanlayıcının tuttuğu ölçüm");
  return setTimeout(() => console.log("zamanlayıcı çalıştı:", olcum.istasyon), 60);
}

console.log("geçici ölçümün değeri:", geciciOlcum());
const kimlik = zamanlayicininTuttugu();

global.gc();
await new Promise((karar) => setTimeout(karar, 10));
global.gc();
console.log("ilk toplama turu bitti");

clearTimeout(kimlik);
global.gc();
await new Promise((karar) => setTimeout(karar, 10));
global.gc();
await new Promise((karar) => setTimeout(karar, 10));
console.log("zamanlayıcı iptal edildikten sonraki tur bitti");
```

```
$ node --expose-gc bellek.mjs
geçici ölçümün değeri: 21.4
toplandı: geçici ölçüm
ilk toplama turu bitti
toplandı: zamanlayıcının tuttuğu ölçüm
zamanlayıcı iptal edildikten sonraki tur bitti
```

Sonuç iki kuralı birden gösterir. Birinci nesne, fonksiyon döndüğü anda erişilemez hale
geldi ve ilk toplama turunda toplandı — döndürülen yalnız bir sayıydı, nesnenin kendisi
değil. İkinci nesne, zamanlayıcı beklediği sürece toplanamadı; ancak `clearTimeout` ile
kök ortadan kalktıktan sonra toplandı.

Buradaki `--expose-gc` seçeneği yalnız gözlem içindir. Uygulama kodunda toplayıcıyı elle
çağırmak ne gereklidir ne de doğrudur; kararı çalışma zamanı verir.

#### Eşzamansız Kökler ve Ölçüm Akışı

Aynı kural, kursun örneğindeki her yapıya uygulanır.

**Kendi kendini zamanlayan akış.** Zamanlayıcılar konusundaki `akisBasla` işlevi, her
turda yeni bir zamanlayıcı kurar. Akış durdurulmadığı sürece geri çağrı ve kapattığı
sayaçlar, tampon, istasyon listesi bellekte kalır. Akışı durduran işlev aynı zamanda bir
bellek işlevidir.

**İptal edilmeyen istek.** Yarış tabanlı zaman aşımında kaybeden söz sonuçlanmaz;
sonuçlanmayan sözün geri çağrıları da kök olduğu için, isteğin tuttuğu tampon ve yanıt
gövdesi serbest bırakılamaz.

**Kaldırılmayan dinleyici.** İptal işareti dersinde dinleyici `once` seçeneğiyle
kaydedilmişti. Seçenek olmasaydı dinleyici işaret nesnesine bağlı kalır, o da geri
çağrının kapattığı her şeyi tutardı.

Üç örneğin ortak dersi şudur: **eşzamansız bir işi başlatmak, aynı zamanda bir kök
oluşturmaktır.** İşi sonlandıran ya da iptal eden kod, o kökü kaldıran koddur.

#### Kapanış Neyi Tutar

Bir kapanışın kendi kapsamındaki *her* değeri tuttuğu sanılır. Çalışma zamanları böyle
davranmak zorunda değildir: yalnız iç fonksiyonun gerçekten başvurduğu bağlamalar
saklanır, diğerleri sıradan yerel değişken gibi ele alınır.

```js
const toplananlar = [];
const kayitDefteri = new FinalizationRegistry((ad) => {
  toplananlar.push(ad);
});

function kapanisKur() {
  const hamTampon = { ad: "ham ölçüm dizisi" };
  const ozet = { istasyon: "A1", deger: 21.4 };
  kayitDefteri.register(hamTampon, "ham tampon");
  kayitDefteri.register(ozet, "özet");

  return function ozetiVer() {
    return ozet.deger;
  };
}

const ozetiVer = kapanisKur();

global.gc();
await new Promise((karar) => setTimeout(karar, 10));
global.gc();
await new Promise((karar) => setTimeout(karar, 10));

console.log("kapanışın verdiği değer:", ozetiVer());
console.log("toplananlar:", toplananlar.sort().join(", ") || "(yok)");
console.log("ölçüm alanları:", Object.keys(process.memoryUsage()).sort().join(", "));
```

```
$ node --expose-gc kapanis.mjs
kapanışın verdiği değer: 21.4
toplananlar: ham tampon
ölçüm alanları: arrayBuffers, external, heapTotal, heapUsed, rss
```

İki nesne de aynı fonksiyonun yerelleriydi; dönen kapanış yalnız birine başvuruyordu.
Başvurulmayan tampon toplandı, başvurulan özet kapanış yaşadığı sürece kaldı.

Buna karşın bu davranış bir güvence değildir; kapanışın hangi bağlamaları sakladığı çalışma
zamanının kararıdır. Taşınabilir kural şudur: **uzun yaşayacak bir kapanışta yalnız
gerekli olanı kapatın.** Büyük bir yapıdan tek bir alan gerekiyorsa, kapanışı yapının
tamamı yerine o alanla kurmak hem açık hem güvenlidir.

Son satır, bellek kullanımının hangi eksenlerde raporlandığını gösterir. Değerleri
makineye ve yüke göre değiştiği için burada yalnız alan adları yazdırıldı: `rss` sürecin
işletim sisteminden aldığı toplam bellek, `heapTotal` ayrılmış öbek boyutu, `heapUsed`
öbekte gerçekten kullanılan bölüm, `external` ile `arrayBuffers` ise JavaScript öbeği
dışında tutulan ikili veridir. Sızıntı takibinde izlenen alan genellikle `heapUsed`
değeridir.

#### Serbest Bırakma Ne Zaman Görünür

Bir nesnenin erişilemez hale gelmesi ile belleğin gerçekten geri verilmesi aynı an değildir.
Toplayıcı kendi zamanlamasına göre çalışır; ölçülen bellek kullanımı erişilemezlikten
hemen sonra düşmeyebilir.

Bunun iki pratik sonucu vardır. Birincisi, "bellek düşmedi" gözlemi tek başına sızıntı
kanıtı değildir; ölçüm birden çok toplama turu boyunca tekrarlanmalıdır. İkincisi,
`FinalizationRegistry` geri çağrısının ne zaman — hatta çalışıp çalışmayacağı — güvence
altında değildir; program sonlanırken hiç çalışmayabilir. Bu nedenle kaynak temizliği
oraya yazılmaz; temizlik açık bir kapatma çağrısıyla ya da `finally` bloğuyla yapılır.

#### Özet

- Bellek yaşam döngüsü ayırma, kullanım ve serbest bırakma aşamalarından oluşur;
  JavaScript'te üçüncüsünü çalışma zamanı yapar.
- Otomatik yönetim sarkan işaretçi hatasını ortadan kaldırır, yerine "gereğinden uzun
  başvurma" hatasını koyar.
- Bir nesne, kök kümesinden erişilemez hale geldiğinde toplanabilir; kökler arasında
  bekleyen zamanlayıcılar, kayıtlı dinleyiciler ve sonuçlanmamış sözler de vardır.
- Eşzamansız bir iş başlatmak bir kök oluşturur; iptal ya da sonlandırma o kökü kaldırır.
- Kapanış yalnız başvurduğu bağlamaları saklayabilir; uzun yaşayan kapanışlarda gereksiz
  değer kapatmamak taşınabilir kuraldır.
- Erişilemezlik ile belleğin geri verilmesi aynı an değildir; sonlandırıcı geri çağrılar
  temizlik için kullanılamaz.

#### Sonraki Adım

Bu derste toplayıcı bir kara kutu olarak kullanıldı: "erişilemeyeni toplar" denildi ve
elle tetiklendi. Sonraki ders kutuyu açar. Erişilebilirliğin nasıl hesaplandığı, başvuru
sayma yönteminin çevrimlerde neden başarısız olduğu, kuşak varsayımının toplayıcıyı nasıl
hızlandırdığı ve zayıf başvuru yapılarının bu düzenekte hangi boşluğu doldurduğu ele
alınacak.

### Çöp Toplama

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/cop-toplama

Erişilebilirlik temelli toplamanın işleyişi, başvuru saymanın çevrimlerde başarısız oluşu, kuşak varsayımı, artımlı toplamanın duraklamaları ve zayıf başvuru yapıları.

Önceki ders toplayıcıyı kara kutu olarak kullandı: "erişilemeyeni toplar" denildi. Bu
ders kutuyu açar. Amaç toplayıcı yazmak değil, davranışını öngörebilmektir — özellikle
duraklamaların nereden geldiğini ve zayıf başvuruların hangi boşluğu doldurduğunu.

#### İşaretle ve Süpür

Temel yöntem iki aşamalıdır. **İşaretleme** aşamasında kök kümesinden başlanır ve
başvurular izlenerek ulaşılan her nesne işaretlenir. **Süpürme** aşamasında işaretlenmemiş
nesnelerin yeri geri alınır.

Bu, Veri Yapıları kursundaki çizge dolaşımının doğrudan uygulanmasıdır: nesneler düğüm,
başvurular kenardır; işaretleme, köklerden başlayan bir erişilebilirlik dolaşımıdır.
Ziyaret işaretlemesi de aynı işi görür — daha önce görülen nesne yeniden dolaşılmaz.

Yöntemin belirleyici özelliği şudur: işaretlemenin maliyeti ölü nesne sayısına değil,
**yaşayan nesne sayısına** bağlıdır — ölü nesneler hiç işaretlenmez. Süpürme ise öbeği
baştan sona tarar; ölü nesnelerin yeri bu taramada geri alınır.

#### Başvuru Sayma Neden Yetmez

Alternatif bir yöntem, her nesnede kendisine kaç başvuru olduğunu saymaktır. Sayı sıfıra
düştüğünde nesne serbest bırakılır. Bu yöntem hızlıdır ve duraklama üretmez; ama bir
sınıfı hiç toplayamaz: birbirine başvuran nesneler.

```js
const toplananlar = [];
const kayitDefteri = new FinalizationRegistry((ad) => {
  toplananlar.push(ad);
});

function cevrimKur() {
  const a = { ad: "A1 düğümü" };
  const b = { ad: "B2 düğümü" };
  a.komsu = b;
  b.komsu = a;
  kayitDefteri.register(a, "çevrimdeki A1");
  kayitDefteri.register(b, "çevrimdeki B2");
  console.log("çevrim kuruldu:", a.komsu.ad, "<->", b.komsu.ad);
}

cevrimKur();

global.gc();
await new Promise((karar) => setTimeout(karar, 10));
global.gc();
await new Promise((karar) => setTimeout(karar, 10));
console.log("toplananlar:", toplananlar.sort().join(", "));
```

```
$ node --expose-gc cevrim.mjs
çevrim kuruldu: B2 düğümü <-> A1 düğümü
toplananlar: çevrimdeki A1, çevrimdeki B2
```

Toplama sırası öngörülemez olduğu için adlar biriktirilip sıralanarak yazdırıldı; bu, bir
sonraki dersteki uyarının erken bir örneğidir — toplayıcının **hangi** nesneyi topladığı
gözlenebilir, **hangi sırayla** topladığı gözleme dayandırılamaz.

İki nesnenin her birine bir başvuru vardı ve bu başvurular hiç sıfırlanmadı; buna karşın
ikisi de toplandı. Nedeni açıktır: kök kümesinden hiçbirine ulaşılamıyordu. Erişilebilirlik
ölçütü, "kaç başvuru var" sorusunu değil "köklerden ulaşılıyor mu" sorusunu sorar ve
çevrimler bu soruyu doğru yanıtlar.

Ayrım pratikte sık karşılaşılan bir yapı için önemlidir: birbirine başvuran iki nesne —
üst ve alt öğe, gözlemci ve gözlenen, iki uçlu bir liste düğümü — JavaScript'te sızıntı
kaynağı değildir. Sızıntı, çevrimden değil kökten gelir.

#### Kuşak Varsayımı

Ölçümler tutarlı bir gözlem verir: **nesnelerin çoğu kısa yaşar.** Bir fonksiyonun içinde
üretilen ara nesneler hemen ölür; uygulama boyunca yaşayan yapılar ise azdır.

Toplayıcılar bu gözlemi kullanır. Öbek en az iki bölgeye ayrılır: yeni nesnelerin
ayrıldığı genç bölge ve toplamayı atlatan nesnelerin taşındığı yaşlı bölge. Genç bölge
sık ve hızlı taranır; yaşlı bölge seyrek taranır.

Sonucu, sık yapılan bir mikro eniyilemenin gereksizliğidir: kısa ömürlü nesne üretmekten
kaçınmak çoğu durumda kazanç getirmez, çünkü toplayıcı zaten bu duruma göre
ayarlanmıştır. Asıl maliyet, yaşlı bölgeye geçen ve orada gereksiz yere kalan nesnelerdir.

#### Duraklamalar

İşaretleme sırasında nesne çizgesi değişmemelidir; aksi halde işaretleme tutarsız olur.
Bu nedenle toplayıcı çalışırken program durur — tek iş parçacıklı bir ortamda bunun
anlamı, olay döngüsünün de durmasıdır.

Duraklamayı küçültmek için iki teknik kullanılır. **Artımlı toplama**, işaretlemeyi küçük
parçalara böler ve aralarında programa sıra verir. **Eşzamanlı toplama**, işaretlemenin
bir bölümünü ayrı bir iş parçacığında yürütür. İkisi birlikte, uzun tek bir duraklamayı
çok sayıda kısa duraklamaya dönüştürür.

Uygulama açısından çıkarım şudur: çöp toplama bir başarım kalemidir ve ana iş parçacığında
görünür. Bir sonraki dersteki tanı yöntemleri ve profilleme, toplama duraklamalarını da
kapsar.

#### Zayıf Başvurular

Bazı durumlarda bir nesneye başvurmak ama onu **yaşatmamak** istenir: bir önbellek, bir
kimlik eşlemesi, bir gözlemci kaydı. Güçlü başvuru bu iş için yanlıştır; nesne artık
kullanılmasa bile önbellekte durduğu için toplanamaz.

Zayıf yapılar bu boşluğu doldurur. `WeakMap` ve `WeakSet`, anahtarlarını yaşatmaz.

```js
const kayitDefteri = new FinalizationRegistry((ad) => {
  console.log("toplandı:", ad);
});

const gucluHarita = new Map();
const zayifHarita = new WeakMap();

function gucluTut() {
  const anahtar = { istasyon: "A1" };
  gucluHarita.set(anahtar, "güçlü haritadaki değer");
  kayitDefteri.register(anahtar, "güçlü haritanın anahtarı");
}

function zayifTut() {
  const anahtar = { istasyon: "B2" };
  zayifHarita.set(anahtar, "zayıf haritadaki değer");
  kayitDefteri.register(anahtar, "zayıf haritanın anahtarı");
}

gucluTut();
zayifTut();

global.gc();
await new Promise((karar) => setTimeout(karar, 10));
global.gc();
await new Promise((karar) => setTimeout(karar, 10));

console.log("güçlü haritadaki giriş sayısı:", gucluHarita.size);
console.log("toplama turu bitti");
```

```
$ node --expose-gc zayif.mjs
toplandı: zayıf haritanın anahtarı
güçlü haritadaki giriş sayısı: 1
toplama turu bitti
```

İki anahtar da fonksiyonlar döndükten sonra erişilemez hale geldi. Zayıf haritadaki
anahtar toplandı; güçlü haritadaki anahtar toplanmadı, çünkü haritanın kendisi ona güçlü
bir başvuru tutuyordu ve harita da köklerden erişilebilirdi.

Buradan çıkan kural doğrudandır: **bir eşlemenin ömrü anahtar nesnenin ömrüne bağlıysa
zayıf yapı kullanılır.** Ölçüm akışında istasyon nesnesi başına tutulan yardımcı veri
buna örnektir; istasyon nesnesi düştüğünde yardımcı veri de düşmelidir.

Zayıf yapıların üç sınırı vardır. Anahtarları yalnız nesne (ES2023'ten beri kayıtlı olmayan
simge de) olabilir. Üzerlerinde gezinme yapılamaz, boyutları sorulamaz — çünkü içerik
toplayıcının kararına bağlı olarak değişir ve bu gözlemlenebilir olsaydı program
belirlenimci olmazdı. `WeakRef` ile tekil bir zayıf başvuru da tutulabilir; ancak `deref`
çağrısının ne zaman boş döneceği öngörülemez, bu yüzden program mantığı buna dayandırılmaz.

#### Kuşak Varsayımını Ölçmek

Kuşak varsayımının doğrudan gözlenebilir bir izi vardır: kısa ömürlü nesnelerden oluşan bir
döngü, öbek kullanımını kalıcı olarak büyütmez. Ölçüm mutlak sayılarla yapılamaz — değerler
çalıştırmaya, sürüme ve makineye göre değişir — ama **ilişki** sınanabilir.

```js
function obek() {
  const b = process.memoryUsage();
  return { heapUsed: Math.round(b.heapUsed / 1024) };
}

const once = obek();

for (let i = 0; i < 200000; i++) {
  const gecici = { sensor: "S-01", deger: i };
  if (gecici.deger < 0) console.log("olmaz");
}

const sonra = obek();
global.gc();
const toplamaSonrasi = obek();

console.log("kısa ömürlü döngü öncesi (KiB):", once.heapUsed > 0);
console.log("toplama sonrası öbek, döngü sonrasından küçük mü:",
            toplamaSonrasi.heapUsed <= sonra.heapUsed);
```

```
$ node --expose-gc kusak.mjs
kısa ömürlü döngü öncesi (KiB): true
toplama sonrası öbek, döngü sonrasından küçük mü: true
```

İki yüz bin nesne yaratıldı ve hiçbiri döngüden sonra erişilebilir değil. Toplama
çağrıldığında öbek kullanımı geri düşer; genç kuşakta ölen nesneler için ödenen bedel,
yaşayanların kopyalanmasıyla sınırlıdır.

Sayı yerine karşılaştırma yazdırmak bilinçlidir: mutlak bayt değerleri her çalıştırmada
farklıdır ve derse yazıldığında yanıltıcı olur. Ölçülebilir olan, kullanımın yönüdür.

#### Toplayıcının Vaat Etmedikleri

Üç noktanın açıkça bilinmesi gerekir.

Toplama anı öngörülemez. "Erişilemez oldu" ile "bellek geri verildi" arasındaki süre
çalışma zamanının kararıdır.

Sonlandırıcı geri çağrılar güvence değildir. Program sonlanırken hiç çalışmayabilir; bu
nedenle dosya, bağlantı ve zamanlayıcı gibi kaynakların temizliği açık çağrılarla yapılır.

Toplayıcı sızıntıyı çözmez. Erişilebilir bir nesne, gereksiz olsa bile toplanmaz. Sızıntı
bir toplayıcı kusuru değil, bir program kusurudur — ve bu nedenle tanısı programda aranır.

#### Özet

- İşaretle-süpür yöntemi köklerden başlayan bir erişilebilirlik dolaşımıdır; maliyeti
  yaşayan nesne sayısına bağlıdır.
- Başvuru sayma çevrimleri toplayamaz; erişilebilirlik ölçütü toplar, bu yüzden karşılıklı
  başvuran nesneler JavaScript'te sızıntı kaynağı değildir.
- Kuşak varsayımı, nesnelerin çoğunun kısa yaşadığı gözlemine dayanır ve genç bölgenin sık
  taranmasını sağlar.
- Toplama ana iş parçacığını durdurur; artımlı ve eşzamanlı teknikler uzun duraklamayı
  kısa duraklamalara böler.
- Zayıf yapılar anahtarlarını yaşatmaz; ömrü başka bir nesnenin ömrüne bağlı eşlemeler
  için kullanılır.

#### Sonraki Adım

Toplayıcının nasıl çalıştığı bilindiğine göre, çalışmadığı durumlar tanınabilir hale
gelir. Sonraki ders sızıntıyı tanımlar ve ölçer: eşzamansız kodda en sık görülen dört
sızıntı biçimi, öbek anlık görüntüsünün ne içerdiği ve iki anlık görüntüyü karşılaştırarak
hangi nesnelerin biriktiğini sayma yöntemi. Ölçüm akışının sızdıran ve sızdırmayan iki
sürümü aynı ölçütle karşılaştırılacak.

### Bellek Sızıntısı Tanısı

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/bellek-sizintisi-tanisi

Sızıntının istenmeyen erişilebilirlik olarak tanımı, eşzamansız kodda dört yaygın kaynak, öbek anlık görüntüsünün içeriği ve iki görüntüyü karşılaştırarak birikimi sayma yöntemi.

Toplayıcı erişilemeyen her nesneyi toplar. O halde bellek neden dolar? Yanıt tanımın
kendisindedir: toplanmayan nesneler **erişilebilir** nesnelerdir. Sızıntı, toplayıcının
kaçırdığı bellek değil, programın farkında olmadan tuttuğu bellektir.

Bu ders sızıntıyı ölçülebilir hale getirir. Önce eşzamansız kodda en sık görülen dört
kaynak tanınır, sonra öbek anlık görüntüsüyle birikim sayılır. Ölçüm akışının sızdıran ve
sınırlı iki sürümü aynı ölçütle karşılaştırılacak.

#### Sızıntının Tanımı

**Bellek sızıntısı**, artık kullanılmayacak bir nesnenin köklerden erişilebilir kalmasıdır.
Tanımda iki parça vardır ve ikisi de gereklidir: nesne gereksizdir *ve* erişilebilirdir.

Bu tanım, sızıntı avını somut bir soruya indirger: **bu nesneye hangi başvuru zinciri
üzerinden ulaşılıyor?** Zincir bulunduğunda düzeltme de bellidir; zinciri kıran satır
yazılır.

Bir uyarı gerekir: artan bellek her zaman sızıntı değildir. Önbellek dolarken bellek
artar ve bu tasarımın gereğidir. Ayırt edici ölçüt, **artışın durup durmadığıdır**. Sınırlı
bir yapı bir noktada dengelenir; sızıntı doğrusal artmayı sürdürür.

#### Eşzamansız Kodda Dört Kaynak

**Sınırsız birikim.** Her sonucu bir diziye ya da haritaya ekleyen ve hiç çıkarmayan kod.
Ölçüm akışında en sık görülen biçimdir: "geçmişi tutalım" ile başlar, sınır konmadığı için
sürer.

**Kaldırılmayan dinleyici.** Bir olay kaynağına eklenen ve hiç kaldırılmayan dinleyici,
kapattığı her şeyi kaynak yaşadığı sürece tutar. Aynı bileşen defalarca kaydolursa
dinleyici sayısı da doğrusal artar; çalışma zamanları belirli bir sayıdan sonra uyarı
verir.

**İptal edilmeyen zamanlayıcı.** Bekleyen bir zamanlayıcı köktür; geri çağrısının kapattığı
her değer bellekte kalır. Kendi kendini zamanlayan bir akış, durdurulmadıkça bunu süresiz
sürdürür.

**Sonuçlanmayan söz.** Beklemede kalan bir söz, kendisine bağlanmış geri çağrıları ve
onların kapattığı değerleri tutar.

Son kaynak en az görünen olduğu için gösterilmeye değer.

```js
const kayitDefteri = new FinalizationRegistry((ad) => {
  console.log("toplandı:", ad);
});

function bekleyenIs() {
  const tampon = { ad: "ölçüm tamponu" };
  kayitDefteri.register(tampon, "sonuçlanmayan sözün tuttuğu tampon");
  const soz = new Promise(() => {
    // Bu söz hiçbir zaman sonuçlanmaz.
  });
  soz.then(() => console.log("işlendi:", tampon.ad));
  return soz;
}

let bekleyen = bekleyenIs();

global.gc();
await new Promise((karar) => setTimeout(karar, 10));
global.gc();
console.log("söze başvuru sürerken toplama turu bitti");

bekleyen = null;
global.gc();
await new Promise((karar) => setTimeout(karar, 10));
global.gc();
await new Promise((karar) => setTimeout(karar, 10));
console.log("başvuru bırakıldıktan sonraki tur bitti");
```

```
$ node --expose-gc bekleyen.mjs
söze başvuru sürerken toplama turu bitti
toplandı: sonuçlanmayan sözün tuttuğu tampon
başvuru bırakıldıktan sonraki tur bitti
```

Başvuru zinciri şudur: modül değişkeni → beklemede söz → kayıtlı tepki → geri çağrının
kapanışı → tampon. Zincirin ilk halkası bırakıldığında tamamı toplandı.

Buradan çıkan pratik kural, iptal ve zaman aşımı dersini bellek tarafından tekrar
gerekçelendirir: her bekleyişin sonlanma güvencesi olmalıdır. Zaman aşımı yalnız bir
kullanıcı deneyimi kararı değil, bir bellek kararıdır.

#### Öbek Anlık Görüntüsü

**Öbek anlık görüntüsü (heap snapshot)**, belirli bir andaki nesne çizgesinin kaydıdır:
düğümler nesnelerdir; kenarlar başvurulardır. Her düğüm için tip, ad ve boyut bilgisi
tutulur.

Görüntüde iki büyüklük ayrı ayrı okunur. **Sığ boyut**, nesnenin kendi kapladığı yerdir.
**Tutulan boyut**, nesne toplandığında geri kazanılacak toplam yerdir — yani yalnız o
nesne üzerinden erişilebilen her şeyin toplamı. Sızıntı avında anlamlı olan ikincisidir:
küçük bir nesne, büyük bir yapıyı tutuyor olabilir.

Görüntü, çalışma zamanının hata ayıklama arayüzünden alınır. Program içinden de
alınabilir; aşağıdaki işlev bunu yapar ve belirli bir sınıfın kaç örneğinin yaşadığını
sayar.

#### İki Görüntüyü Karşılaştırmak

Tek bir görüntü "çok nesne var" der; sızıntıyı göstermez. Yöntem, aynı işi tekrarlayıp
görüntüleri karşılaştırmaktır:

1. İşlem başlamadan bir görüntü alınır — taban çizgisi.
2. İşlem birçok kez tekrarlanır.
3. İkinci görüntü alınır ve fark incelenir.

Beklenti, işlemin ürettiği geçici nesnelerin ikinci görüntüde bulunmamasıdır. Bulunuyorsa
ve sayıları tekrar sayısıyla orantılıysa, birikim vardır.

```js
import { Session } from "node:inspector/promises";

class Olcum {
  constructor(istasyon, deger) {
    this.istasyon = istasyon;
    this.deger = deger;
  }
}

async function ornekSayisi(sinifAdi) {
  const oturum = new Session();
  oturum.connect();
  const parcalar = [];
  oturum.on("HeapProfiler.addHeapSnapshotChunk", (ileti) => parcalar.push(ileti.params.chunk));
  await oturum.post("HeapProfiler.takeHeapSnapshot", { reportProgress: false });
  oturum.disconnect();

  const anlik = JSON.parse(parcalar.join(""));
  const alanlar = anlik.snapshot.meta.node_fields;
  const tipDizini = alanlar.indexOf("type");
  const adDizini = alanlar.indexOf("name");
  const nesneTipi = anlik.snapshot.meta.node_types[tipDizini].indexOf("object");

  let sayi = 0;
  for (let i = 0; i < anlik.nodes.length; i += alanlar.length) {
    const tip = anlik.nodes[i + tipDizini];
    const ad = anlik.strings[anlik.nodes[i + adDizini]];
    if (tip === nesneTipi && ad === sinifAdi) sayi += 1;
  }
  return sayi;
}

const gecmis = [];
function sizdiranIsle(olcum) {
  gecmis.push(olcum);
}

const pencere = [];
function sinirliIsle(olcum) {
  pencere.push(olcum);
  if (pencere.length > 10) pencere.shift();
}

console.log("1. anlık görüntü — Olcum örneği:", await ornekSayisi("Olcum"));

for (let i = 0; i < 500; i += 1) sizdiranIsle(new Olcum("A1", 21.4));
console.log("2. anlık görüntü — sızdıran işleyiciden sonra:", await ornekSayisi("Olcum"));

gecmis.length = 0;
for (let i = 0; i < 500; i += 1) sinirliIsle(new Olcum("A1", 21.4));
console.log("3. anlık görüntü — sınırlı işleyiciden sonra:", await ornekSayisi("Olcum"));
```

```
1. anlık görüntü — Olcum örneği: 0
2. anlık görüntü — sızdıran işleyiciden sonra: 500
3. anlık görüntü — sınırlı işleyiciden sonra: 10
```

Sayılar tanıyı doğrudan verir. Beş yüz ölçüm işlendi; sızdıran işleyiciden sonra beş
yüzünün beş yüzü de bellekte. Sınırlı işleyicide ise pencere boyu kadar — on — nesne
kaldı, geri kalanı toplandı.

Ölçütün gücü, oranın kendisindedir: örnek sayısı işlem sayısıyla orantılı artıyorsa
sızıntı kesindir. Sabit bir üst sınırda duruyorsa yapı sınırlıdır.

Anlık görüntü almanın kendisi de bir çöp toplama turu tetikler; bu nedenle sayımda
yalnız gerçekten erişilebilir olan nesneler görünür. Sayının kararlı çıkmasının nedeni
budur.

#### Tanıdan Düzeltmeye

Sayım, hangi sınıfın biriktiğini söyler; başvuru zincirini söylemez. Arayüz tabanlı
araçlarda zincir, "tutan yol" görünümüyle doğrudan okunur. Program içinden yapılan bir
sayımda ise zincir, kodun kendisinden çıkarılır: nesneyi hangi yapı tutuyor, o yapı
neden büyüyor.

Düzeltme üç kalıptan birine indirgenir. Sınır koymak — pencere, kapasiteli önbellek,
en eski girdiyi düşüren politika. Bağı kaldırmak — dinleyiciyi kaldırmak, zamanlayıcıyı
iptal etmek, isteği durdurmak. Zayıf başvuruya geçmek — eşlemenin ömrü anahtar nesnenin
ömrüne bağlıysa.

#### Özet

- Sızıntı, gereksiz bir nesnenin köklerden erişilebilir kalmasıdır; toplayıcı kusuru değil
  program kusurudur.
- Eşzamansız kodda dört yaygın kaynak vardır: sınırsız birikim, kaldırılmayan dinleyici,
  iptal edilmeyen zamanlayıcı ve sonuçlanmayan söz.
- Öbek anlık görüntüsü nesne çizgesinin kaydıdır; sızıntı avında tutulan boyut sığ boyuttan
  daha bilgilendiricidir.
- Tanı tek görüntüyle değil, aynı işlemin tekrarından önce ve sonra alınan iki görüntünün
  karşılaştırılmasıyla konur.
- Örnek sayısı tekrar sayısıyla orantılı artıyorsa sızıntı vardır; sabit bir sınırda
  duruyorsa yapı sınırlıdır.

#### Sonraki Adım

Bellek tarafı tamamlandı. Geriye zaman tarafı kalıyor: bir program bellek sızdırmadan da
yavaş olabilir. Sonraki ders ana iş parçacığının zaman çizelgesini okur — uzun görevin
tanımı, olay döngüsü gecikmesinin ne anlama geldiği, örnekleyici profilcinin ne kaydettiği
ve uzun bir işi bölerek araya sıra vermenin gözlenebilir etkisi.

### Başarım Profilleme

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/basarim-profilleme

Ana iş parçacığı zaman çizelgesinin okunması, uzun görev kavramı, olay döngüsü gecikmesi, örnekleyici profilcinin kaydettikleri ve uzun işi bölerek sıra vermenin gözlenebilir etkisi.

Bir program bellek sızdırmadan da kötü çalışabilir. Kullanıcının gördüğü şey bellek
grafiği değil, tıklamaya geç yanıt veren bir arayüz ya da yanıt süresi dalgalanan bir
hizmettir.

Bu dersin ölçütü bellekten farklıdır: iş parçacığının **ne kadar süreyle tek bir işe
kilitlendiği**. Kursun ilk dersindeki engelleme kavramı burada ölçülebilir hale gelir.

Ders boyunca hiçbir süre değeri yazdırılmayacak; süreler makineye ve yüke göre değişir ve
yazıldıklarında yanıltıcı olurlar. Ölçülen şey sıradır; gösterilen şey yöntemdir.

#### Uzun Görev

Olay döngüsü, her turda görev kuyruğundan bir iş alır ve sonuna kadar çalıştırır. Bu iş
uzun sürerse, o süre boyunca hiçbir tıklama işlenmez, hiçbir yanıt ayrıştırılmaz, hiçbir
çizim yapılmaz.

Belirli bir eşiği aşan görevlere **uzun görev (long task)** denir. Eşik ortama göre
tanımlanır; tarayıcı ölçüm arayüzlerinde elli milisaniye yaygın bir sınırdır. Sayının
kendisi değil, ardındaki akıl yürütme önemlidir: kullanıcı etkileşiminin akıcı sayılması
için iş parçacığının düzenli aralıklarla serbest kalması gerekir.

Uzun görevin ikinci ölçüsü **olay döngüsü gecikmesi (event loop lag)** olarak adlandırılır.
Yöntemi doğrudandır: bilinen bir gecikmeyle bir zamanlayıcı kurulur ve geri çağrının
gerçekte ne kadar geç çalıştığına bakılır. Fark, o anda iş parçacığının ne kadar meşgul
olduğunun ölçüsüdür. Sunucu tarafında bu değer, yanıt sürelerinin dalgalanmasını
açıklayan ilk göstergedir.

#### Bölmek ve Sıra Vermek

Uzun bir işi hızlandırmak her zaman mümkün değildir; ama bölmek çoğu zaman mümkündür.
Bölünmüş iş, parçalar arasında olay döngüsüne sıra verir ve bekleyen görevler araya girer.

```js
function meshgulBekle(sure) {
  const bitis = Date.now() + sure;
  while (Date.now() < bitis) {
    // İş parçacığını kasten meşgul tutar.
  }
}

function siraVer() {
  return new Promise((karar) => setTimeout(karar, 0));
}

async function tekParcaIsle() {
  setTimeout(() => console.log("  araya giren görev"), 0);
  meshgulBekle(60);
  console.log("  işleme bitti");
  await siraVer();
}

async function parcaliIsle() {
  setTimeout(() => console.log("  araya giren görev"), 0);
  for (let i = 1; i <= 3; i += 1) {
    meshgulBekle(20);
    console.log("  parça", i, "bitti");
    await siraVer();
  }
}

console.log("tek parçalı işleme:");
await tekParcaIsle();

console.log("bölünmüş işleme:");
await parcaliIsle();
```

```
tek parçalı işleme:
  işleme bitti
  araya giren görev
bölünmüş işleme:
  parça 1 bitti
  araya giren görev
  parça 2 bitti
  parça 3 bitti
```

Tek parçalı işlemede bekleyen görev en sona kaldı; bölünmüş işlemede birinci parçadan
hemen sonra araya girdi. Toplam iş aynıdır — her iki durumda da altmış milisaniyelik
meşguliyet vardır. Değişen, bekleyen işin ne kadar beklediğidir.

Sıra vermenin bir mikro görevle yapılamayacağı da burada görülür. Olay döngüsü dersindeki
kural gereği mikro görev kuyruğu tamamen boşaltılır; `queueMicrotask` ya da beklemede
olmayan bir söz beklemek görev kuyruğuna sıra vermez. Gerçek sıra, görev kuyruğuna
dönmekle verilir.

Bölmenin bedeli de vardır: parça sayısı arttıkça tur başına ek maliyet ödenir ve toplam
süre uzar. Doğru parça boyutu, "tepki verilebilir kalmak" ile "verimli çalışmak"
arasındaki dengedir. Hesap yükü gerçekten ağırsa doğru yanıt bölmek değil, işi ayrı bir
yürütme bağlamına taşımaktır.

#### Ölçüm İmleri

Nerede zaman harcandığını görmek için önce ölçülecek aralıkların adlandırılması gerekir.
Standart ölçüm arayüzü iki kavram sunar: belirli bir anı işaretleyen **im (mark)** ve iki
im arasını adlandıran **ölçüm (measure)**.

```js
import { PerformanceObserver, performance } from "node:perf_hooks";

function meshgulBekle(sure) {
  const bitis = Date.now() + sure;
  while (Date.now() < bitis) {
    // İş parçacığını kasten meşgul tutar.
  }
}

const gozlemci = new PerformanceObserver((liste) => {
  for (const giris of liste.getEntries()) {
    console.log("kayıt:", giris.entryType, "—", giris.name, "— süre ölçüldü mü:", giris.duration > 0);
  }
});
gozlemci.observe({ entryTypes: ["measure"] });

performance.mark("ayrıştırma-başladı");
meshgulBekle(20);
performance.mark("ayrıştırma-bitti");
meshgulBekle(10);
performance.mark("yazma-bitti");

performance.measure("ayrıştırma", "ayrıştırma-başladı", "ayrıştırma-bitti");
performance.measure("yazma", "ayrıştırma-bitti", "yazma-bitti");
performance.measure("toplam", "ayrıştırma-başladı", "yazma-bitti");

await new Promise((karar) => setTimeout(karar, 10));
gozlemci.disconnect();
```

```
kayıt: measure — ayrıştırma — süre ölçüldü mü: true
kayıt: measure — toplam — süre ölçüldü mü: true
kayıt: measure — yazma — süre ölçüldü mü: true
```

Süre değerleri yazdırılmadı; yalnız ölçümün gerçekleştiği doğrulandı. Çıktı sırası da
öğreticidir: kayıtlar oluşturulma sırasına göre değil **başlangıç anına** göre sıralanır.
"toplam" ölçümü en son oluşturuldu ama "ayrıştırma" ile aynı imden başladığı için ikinci
sırada göründü.

İmlerin değeri, ölçümü koda gömülü ve adlandırılmış hale getirmesidir. Eşzamansız bir
akışta bir işlemin başlangıcı ve bitişi farklı turlarda gerçekleşir; im olmadan bu iki an
aynı ölçüde bağlanamaz.

#### Örnekleyici Profil

İm koymak, nereye bakılacağı bilindiğinde işe yarar. Bilinmediğinde profilci kullanılır.

Yaygın yöntem **örneklemedir (sampling)**: profilci düzenli aralıklarla çağrı yığıtının
anlık kopyasını alır. Sonuçta hangi işlevin kaç örnekte yığıtın tepesinde olduğu bilinir;
bu, işlevlere düşen süre payının istatistiksel tahminini verir.

Yöntemin iki özelliği sonucun okunuşunu belirler. Ölçüm yaklaşıktır — çok kısa süren bir
işlev hiçbir örneğe düşmeyebilir. Buna karşılık maliyeti düşüktür ve programın davranışını
belirgin biçimde değiştirmez.

Profil, çalışma zamanının hata ayıklama arayüzünden alınır ve düğümleri çağrı çerçevesi
bilgisi taşır.

```js
import { Session } from "node:inspector/promises";

function meshgulBekle(sure) {
  const bitis = Date.now() + sure;
  while (Date.now() < bitis) {
    // İş parçacığını kasten meşgul tutar.
  }
}

const oturum = new Session();
oturum.connect();
await oturum.post("Profiler.enable");
await oturum.post("Profiler.start");

meshgulBekle(60);

const { profile } = await oturum.post("Profiler.stop");
oturum.disconnect();

console.log("profil alanları:", Object.keys(profile).sort().join(", "));
console.log("örnek alındı mı:", profile.samples.length > 0);
console.log("düğümlerde kendi işlevimiz var mı:", profile.nodes.some((d) => d.callFrame.functionName === "meshgulBekle"));
```

```
profil alanları: endTime, nodes, samples, startTime, timeDeltas
örnek alındı mı: true
düğümlerde kendi işlevimiz var mı: true
```

Alan adları profilin yapısını açıklar. `nodes`, çağrı ağacının düğümleridir; her düğüm bir
işlevi ve onun çağrı çerçevesini temsil eder. `samples`, alınan her örnekte yığıtın
tepesindeki düğümün kimliğidir. `timeDeltas`, ardışık örnekler arasındaki süredir. Arayüz
tabanlı araçların çizdiği alev grafiği, bu üç dizinin görselleştirilmesinden başka bir şey
değildir.

Grafiği okumanın kuralı da buradan çıkar: **genişlik süredir; yükseklik çağrı derinliğidir.**
Geniş ve alçak bir blok, tek bir işlevin uzun süre çalıştığını; dar ve derin bir yığıt ise
çok sayıda kısa çağrıyı gösterir.

#### Ne Ölçülmemeli

Üç yaygın hata sonuçları kullanılamaz hale getirir.

Tek bir çalıştırmanın süresi ölçüm sayılmaz. İlk çalıştırmada derleme ve önbellek ısınması
maliyeti vardır; ölçüm tekrarlanmalı ve dağılıma bakılmalıdır.

Ortalama tek başına yeterli değildir. Kullanıcı deneyimini belirleyen, uç değerlerdir;
yavaş yüzdelik dilimler ayrıca izlenmelidir.

Eşzamansız bir akışın "süresi", işlevlerin toplam çalışma süresi değildir. Beklemede
geçen zaman iş parçacığını meşgul etmez; profilcide görünmez ama kullanıcı için gerçektir.
Bu nedenle profil ile uçtan uca ölçüm birlikte okunur.

#### Özet

- Uzun görev, iş parçacığını tek bir işe kilitleyen ve bekleyen bütün işleri geciktiren
  görevdir; olay döngüsü gecikmesi bunun ölçüsüdür.
- Uzun bir işi bölüp görev kuyruğuna sıra vermek, bekleyen işlerin araya girmesini sağlar;
  mikro görevle sıra verilemez.
- İm ve ölçüm arayüzü, eşzamansız akışta farklı turlara düşen anları adlandırılmış
  aralıklara bağlar; kayıtlar başlangıç anına göre sıralanır.
- Örnekleyici profilci, düzenli aralıklarla çağrı yığıtının kopyasını alır; sonucu çağrı
  ağacı düğümleri, örnekler ve süre farklarından oluşur.
- Tek çalıştırma, yalnız ortalama ve yalnız profil verisi yanıltıcıdır; ölçüm tekrar,
  dağılım ve uçtan uca süreyle birlikte okunur.

#### Sonraki Adım

Ölçüm nerede zaman harcandığını söyler, ama neyin yanlış olduğunu söylemez. Kursun son
dersi tanı araçlarının ikinci yarısını ele alıyor: programı belirli bir noktada durdurup
durumunu incelemek. Kesme noktaları, adımlama, gözlem ifadeleri ve eşzamansız çağrı
yığıtlarının okunması — hepsi ölçüm akışının aynı örneği üzerinde gösterilecek.

### Hata Ayıklama Araçları

Source: https://academia.sh/tr/kurslar/eszamansiz-javascript/hata-ayiklama-araclari

Hata ayıklama arayüzüne bağlanma, kesme noktaları ve koşullu duraklar, gözlem ifadeleri, adımlama ve eşzamansız çağrı yığıtlarının okunması.

Profilleme nerede zaman harcandığını söyledi; neyin yanlış olduğunu söylemedi. Bunun için
programı belirli bir noktada durdurup değişkenlerine bakmak gerekir.

Bu ders tanı araçlarının ikinci yarısını ele alır. Örnekler ölçüm akışının aynı işleme
adımı üzerinde kurulacak; kullanılan düzenek, arayüz tabanlı araçların da altında çalışan
hata ayıklama arayüzüdür.

#### Hata Ayıklama Arayüzü

Çalışma zamanı, kendisine bağlanan bir istemcinin programı durdurmasına, değişken
okumasına ve adım adım ilerletmesine izin veren bir arayüz sunar. Tarayıcı geliştirici
araçları, editör eklentileri ve komut satırı istemcileri aynı arayüzü kullanır.

Bağlanmanın üç yolu vardır. Çalışma zamanı ilgili seçenekle başlatılır ve bir istemci
bağlanır; başlangıçta duraklatan seçenekle başlatılır, böylece ilk satırdan itibaren
izlenir; ya da kodun içine `debugger` deyimi konur — bir istemci bağlıysa yürütme orada
durur.

Aşağıdaki örneklerde istemci yerine programın kendisi kullanılıyor: aynı arayüze program
içinden bağlanılıyor. Bu, arayüz tabanlı araçların hangi ilkelerle çalıştığını doğrudan
gösterir.

#### Kesme Noktası, Koşul ve Gözlem İfadeleri

**Kesme noktası (breakpoint)**, yürütmenin durdurulacağı yerdir. Durak anında iki şey
incelenebilir: yerel değişkenlerin değerleri ve çağrı yığıtı.

Her durakta durmak, çok tekrarlı bir akışta işe yaramaz. **Koşullu kesme noktası**, yalnız
verilen ifade doğru olduğunda durur. Aşağıdaki program bunu, durak anında bir ifade
değerlendirip koşul sağlanmadığında hemen devam ederek gerçekleştirir.

```js
import { Session } from "node:inspector";

const oturum = new Session();
oturum.connect();
oturum.post("Debugger.enable");

function ifadeyiOku(cerceve, ifade) {
  let sonuc = null;
  oturum.post(
    "Debugger.evaluateOnCallFrame",
    { callFrameId: cerceve.callFrameId, expression: ifade },
    (hata, yanit) => {
      sonuc = yanit.result.value ?? yanit.result.description;
    },
  );
  return sonuc;
}

oturum.on("Debugger.paused", (olay) => {
  const cerceveler = olay.params.callFrames;
  const ust = cerceveler[0];
  const deger = ifadeyiOku(ust, "olcum.deger");

  if (deger > 20) {
    console.log("durak:", ust.functionName, "— satır", ust.location.lineNumber + 1);
    console.log("  gözlem: olcum.istasyon =", ifadeyiOku(ust, "olcum.istasyon"));
    console.log("  gözlem: olcum.deger =", deger);
    console.log("  yığıt:", cerceveler.slice(0, 3).map((c) => c.functionName).join(" <- "));
  }
  oturum.post("Debugger.resume");
});

function olcumIsle(olcum) {
  debugger;
  return { istasyon: olcum.istasyon, yuvarlanmis: Math.round(olcum.deger) };
}

function akisiIsle(olcumler) {
  return olcumler.map(function tekOlcumu(olcum) {
    return olcumIsle(olcum);
  });
}

const sonuc = akisiIsle([
  { istasyon: "A1", deger: 21.4 },
  { istasyon: "B2", deger: 19.8 },
  { istasyon: "C3", deger: 23.1 },
]);

console.log("sonuç:", sonuc.map((s) => `${s.istasyon}=${s.yuvarlanmis}`).join(" "));
oturum.disconnect();
```

```
durak: olcumIsle — satır 34
  gözlem: olcum.istasyon = A1
  gözlem: olcum.deger = 21.4
  yığıt: olcumIsle <- tekOlcumu <- akisiIsle
durak: olcumIsle — satır 34
  gözlem: olcum.istasyon = C3
  gözlem: olcum.deger = 23.1
  yığıt: olcumIsle <- tekOlcumu <- akisiIsle
sonuç: A1=21 B2=20 C3=23
```

Üç ölçümden ikisi koşulu sağladı ve bildirildi; ikinci ölçümde program hiç
duraklamamış gibi devam etti. Yazdırılan satır numarası, kaynak dosyadaki `debugger`
deyiminin bulunduğu satırdır; kod değişirse bu sayı da değişir.

**Gözlem ifadesi (watch expression)**, durak anında değerlendirilen ifadedir. Yalnız
değişken adı olmak zorunda değildir; `olcum.deger > 20` gibi bir ifade de, bir yöntem
çağrısı da değerlendirilebilir. Arayüz tabanlı araçlarda bu ifadeler bir listeye yazılır
ve her durakta yeniden hesaplanır.

Çağrı yığıtı görünümü de aynı durak verisinden gelir: `olcumIsle` çağrısına `tekOlcumu`
üzerinden, oraya da `akisiIsle` üzerinden gelinmiş. Bu zincir, "bu fonksiyon buraya nereden
çağrıldı" sorusunun yanıtıdır.

#### Adımlama

Durakta üç ilerletme komutu vardır. **Üzerinden atlama (step over)** bir sonraki satıra
geçer; çağrılan fonksiyonların içine girmez. **İçine girme (step into)** çağrılan
fonksiyonun ilk satırında durur. **Dışına çıkma (step out)** içinde bulunulan fonksiyon
dönene kadar çalıştırır.

```js
import { Session } from "node:inspector";

const oturum = new Session();
oturum.connect();
oturum.post("Debugger.enable");

let adim = 0;

oturum.on("Debugger.paused", (olay) => {
  const ust = olay.params.callFrames[0];
  console.log("adım", adim, "— satır", ust.location.lineNumber + 1);
  adim += 1;
  if (adim < 8) oturum.post("Debugger.stepOver");
  else oturum.post("Debugger.resume");
});

function ortalamaAl(olcumler) {
  debugger;
  let toplam = 0;
  for (const olcum of olcumler) {
    toplam += olcum.deger;
  }
  const ortalama = toplam / olcumler.length;
  return ortalama;
}

const sonuc = ortalamaAl([
  { istasyon: "A1", deger: 21.4 },
  { istasyon: "B2", deger: 19.8 },
]);

console.log("ortalama:", sonuc.toFixed(2));
oturum.disconnect();
```

```
adım 0 — satır 18
adım 1 — satır 19
adım 2 — satır 20
adım 3 — satır 20
adım 4 — satır 21
adım 5 — satır 20
adım 6 — satır 21
adım 7 — satır 20
ortalama: 20.60
```

Satır dizisi döngünün yapısını açığa çıkarır: yirminci satır döngü başlığıdır ve **dört
kez** görünür — bir kez döngü kurulup yineleyici alınırken, sonra her yineleyici
çağrısında bir kez: ikisi birer ölçüm getirir, sonuncusu dizinin tükendiğini bildirir.
Yirmi birinci satır gövdedir ve iki ölçüm için iki kez çalışır.

Adımlamanın eşzamansız kodda bir sınırı vardır: `await` ifadesinin üzerinden atlandığında
yürütme o fonksiyondan çıkar ve olay döngüsüne döner. Devamı, ilgili söz sonuçlandığında
başka bir turda çalışır. Bu nedenle eşzamansız akışlarda adımlama yerine, ilgilenilen
noktaya doğrudan kesme noktası koymak daha verimlidir.

#### Eşzamansız Çağrı Yığıtı

Kursun ilk konusunda gösterildiği gibi, bir geri çağrı boş yığıtta çalışır. Bunun tanı
açısından sonucu ağırdır: geri çağrı içinde oluşan bir hatanın yığıtında, o işi *kim
başlattı* bilgisi yoktur.

Çalışma zamanları bu boşluğu eşzamansız çağrı yığıtlarıyla kapatır: `await` ile birbirine
bağlanan fonksiyonların zinciri korunur ve hata yığıtında `async` işaretiyle görünür.

```js
function cerceveleriYaz(hata) {
  const cerceveler = hata.stack
    .split("\n")
    .slice(1)
    .map((satir) => satir.trim())
    .filter((satir) => !satir.includes("node:"))
    .map((satir) => satir.replace(/\s*\(?file:.*$/, ""))
    .filter((satir) => satir !== "at async");
  for (const cerceve of cerceveler) console.log("  ", cerceve);
}

async function olcumGetir(istasyon) {
  await new Promise((karar) => setTimeout(karar, 5));
  throw new Error(`istasyon yanıt vermedi: ${istasyon}`);
}

async function olcumOku(istasyon) {
  return await olcumGetir(istasyon);
}

async function akisiIsle(istasyonlar) {
  const sonuclar = [];
  for (const istasyon of istasyonlar) {
    sonuclar.push(await olcumOku(istasyon));
  }
  return sonuclar;
}

function geriCagriylaGetir(istasyon, gericagri) {
  setTimeout(() => gericagri(new Error(`istasyon yanıt vermedi: ${istasyon}`)), 5);
}

try {
  await akisiIsle(["A1"]);
} catch (hata) {
  console.log("await zinciri —", hata.message);
  cerceveleriYaz(hata);
}

geriCagriylaGetir("B2", (hata) => {
  console.log("geri çağrı zinciri —", hata.message);
  cerceveleriYaz(hata);
});
```

```
await zinciri — istasyon yanıt vermedi: A1
   at olcumGetir
   at async olcumOku
   at async akisiIsle
geri çağrı zinciri — istasyon yanıt vermedi: B2
   at Timeout._onTimeout
```

Fark çarpıcıdır. `await` zincirinde hatanın yolu baştan sona görünüyor: hangi istasyon
okunurken, hangi akış işlenirken oluştuğu okunabiliyor. Geri çağrı zincirinde ise yalnız
zamanlayıcı çerçevesi var; isteği kimin başlattığı bilgisi kaybolmuş.

Çıktıdaki dosya yolları ve çalışma zamanına ait iç çerçeveler burada elenerek yazdırıldı;
gerçek bir yığıtta bunlar da bulunur ve makineye göre değişir. Arayüz tabanlı araçlar aynı
elemeyi "kütüphane kodunu gizle" biçiminde yapar.

Buradan çıkan tasarım kuralı, kursun geri çağrı dersindeki gerekçeye bir yenisini ekler:
sözlerle ve `await` ile yazılmış kod yalnız daha okunaklı değil, **daha tanılanabilirdir**.

#### Yazdırarak Hata Ayıklama

Konsola yazdırmak ilkel görünse de bazı durumlarda tek uygulanabilir yöntemdir: sorun
seyrek ortaya çıkıyorsa, üretim ortamındaysa ya da durdurmanın kendisi davranışı
değiştiriyorsa.

İki iyileştirme onu ciddi bir araç haline getirir. Birincisi, kayıtların **bağlam
taşımasıdır**: hangi istasyon, hangi deneme, hangi işlem anahtarı. Bağlamsız bir "buraya
geldi" satırı, eşzamansız bir akışta hangi isteğe ait olduğunu söylemez.

İkincisi, **kayıt noktası (logpoint)** kullanmaktır: kesme noktası gibi kurulur, ama
durdurmak yerine bir ifade yazdırıp devam eder. Böylece kod değiştirilmeden ve yeniden
başlatılmadan gözlem eklenebilir.

#### Özet

- Hata ayıklama arayüzü, programı durdurma, değişken okuma ve adım adım ilerletme
  işlemlerini sunar; arayüz tabanlı araçlar da bu arayüzü kullanır.
- Kesme noktası bir koşula bağlanabilir; gözlem ifadeleri durak anında değerlendirilir ve
  çağrı yığıtı çağrının nereden geldiğini gösterir.
- Adımlama komutları üzerinden atlama, içine girme ve dışına çıkma biçimindedir; `await`
  üzerinden atlandığında yürütme olay döngüsüne döner.
- Eşzamansız çağrı yığıtı, `await` ile bağlanan zinciri korur; geri çağrı tabanlı kodda
  bu zincir kaybolur.
- Yazdırarak hata ayıklama, kayıtlar bağlam taşıdığında ve kayıt noktaları kullanıldığında
  ciddi bir araçtır.

#### Kurs Kapanışı

Kurs tek bir soruyla başladı: tek iş parçacıklı bir dil, beklemeyi nasıl karşılar? Yanıt
adım adım kuruldu.

Önce **model** kuruldu: kullanıcı kodu tek iş parçacığında, sonuna kadar çalışarak yürür;
olay döngüsü her turda görev kuyruğundan bir iş alır ve ardından mikro görev kuyruğunu
tamamen boşaltır. Bu kural, kurstaki her çıktı sırasının gerekçesiydi.

Sonra **soyutlama** kuruldu: geri çağrının bıraktığı boşluk — birleştirilememe, hata
yolunun elle taşınması, sözleşme ihlalleri — sözün durum makinesiyle kapandı.
Birleştiriciler beklemenin yapısını verdi, `async` ve `await` ise okunuşu düzeltti;
ikisi de sözün üzerine oturan katmanlardı, yeni bir eşzamansızlık düzeneği değil.

Ardından **denetim** kuruldu: iptal işareti, zaman aşımı ve yeniden deneme kalıpları.
Buradaki ayrım kursun en pratik dersidir — beklemeyi kesmek işi kesmez.

Son olarak **gözlem** kuruldu: erişilebilirlik temelli bellek modeli, anlık görüntü
karşılaştırmasıyla sızıntı tanısı, ana iş parçacığı profillemesi ve kesme noktalarıyla
durum incelemesi. Ölçüm akışı örneği, tek bir zamanlayıcı geri çağrısından başlayıp
iptal edilebilir, zaman aşımlı, dayanıklı ve sızıntı bırakmayan bir akışa dönüştü.

Kalan konu, bu kodun nasıl paketlendiği ve dağıtıldığıdır. Sonraki kurs — Modüller,
Araçlar ve Ekosistem — modül sistemlerini, paketlerin çözümlenme biçimini, bağımlılık
sürümlemesini ve derleme araçlarını ele alır. Bu kursta birkaç kez modül biçimine
değinilmiş ve ayrıntısı oraya bırakılmıştı; sıra oraya geldi.

