JavaScript Generators ve Iterators

JavaScript Generators ve Iterators
Generator fonksiyonlar, çalışmasını duraklatıp devam ettirebilen özel fonksiyonlardır. Iterator protokolü ile birlikte tembel değerlendirme (lazy evaluation), sonsuz diziler, asenkron akış kontrolü ve özel veri yapısı iterasyonu gibi güçlü pattern'leri mümkün kılar. Bu rehberde generator ve iterator kavramlarını derinlemesine inceliyoruz.
1. Iterator Protokolü
Iterator protokolü, bir nesnenin değer dizisini nasıl ürettiğini tanımlayan standarttır. Symbol.iterator metodu dönen bir nesne, next() metodu ile { value, done } nesneleri üretir.
// Özel iterable nesne
class Aralik {
constructor(baslangic, bitis, adim = 1) {
this.baslangic = baslangic;
this.bitis = bitis;
this.adim = adim;
}
[Symbol.iterator]() {
let mevcut = this.baslangic;
const bitis = this.bitis;
const adim = this.adim;
return {
next() {
if (mevcut <= bitis) {
const deger = mevcut;
mevcut += adim;
return { value: deger, done: false };
}
return { done: true };
}
};
}
}
// Kullanım
const sayilar = new Aralik(1, 10, 2);
for (const sayi of sayilar) {
console.log(sayi); // 1, 3, 5, 7, 9
}
// Spread ile dizi oluşturma
const dizi = [...new Aralik(0, 5)]; // [0, 1, 2, 3, 4, 5]
// Destructuring ile
const [ilk, ikinci, ucuncu] = new Aralik(100, 110);
console.log(ilk, ikinci, ucuncu); // 100, 101, 102
2. Generator Fonksiyonlar
Generator fonksiyonlar function* sözdizimi ile tanımlanır ve yield ifadesi ile değer üretir. Her yield fonksiyonu duraklatır, next() çağrısı devam ettirir.
// Temel generator
function* sayiUret() {
console.log("Başladı");
yield 1;
console.log("İlk yield sonrası");
yield 2;
console.log("İkinci yield sonrası");
yield 3;
console.log("Bitti");
}
const gen = sayiUret();
console.log(gen.next()); // "Başladı" → { value: 1, done: false }
console.log(gen.next()); // "İlk yield sonrası" → { value: 2, done: false }
console.log(gen.next()); // "İkinci yield sonrası" → { value: 3, done: false }
console.log(gen.next()); // "Bitti" → { value: undefined, done: true }
// Sonsuz dizi
function* sonsuzSayac(baslangic = 0) {
let sayi = baslangic;
while (true) {
yield sayi++;
}
}
const sayac = sonsuzSayac(1);
console.log(sayac.next().value); // 1
console.log(sayac.next().value); // 2
// Sonsuz ama tembel — sadece istendiğinde hesaplar
// Fibonacci generator
function* fibonacci() {
let [onceki, mevcut] = [0, 1];
while (true) {
yield mevcut;
[onceki, mevcut] = [mevcut, onceki + mevcut];
}
}
// İlk 10 Fibonacci sayısı
function al(n, iterator) {
const sonuclar = [];
for (const deger of iterator) {
sonuclar.push(deger);
if (sonuclar.length >= n) break;
}
return sonuclar;
}
console.log(al(10, fibonacci()));
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
3. yield* ile Delegasyon ve Çift Yönlü İletişim
yield* ifadesi başka bir iterable veya generator'a delegasyon yapar. next(deger) ile generator'a dışarıdan değer gönderilebilir.
// yield* delegasyonu
function* harfler() {
yield* "abc"; // String iterable
yield* [1, 2, 3]; // Array iterable
}
console.log([...harfler()]); // ["a", "b", "c", 1, 2, 3]
// Generator kompozisyonu
function* ciftSayilar(limit) {
for (let i = 0; i < limit; i += 2) yield i;
}
function* tekSayilar(limit) {
for (let i = 1; i < limit; i += 2) yield i;
}
function* tumSayilar(limit) {
yield* ciftSayilar(limit);
yield* tekSayilar(limit);
}
// Çift yönlü iletişim
function* sohbet() {
const isim = yield "Adınız nedir?";
const yas = yield `Merhaba ${isim}! Yaşınız?`;
yield `${isim}, ${yas} yaşındasınız. Hoş geldiniz!`;
}
const konusma = sohbet();
console.log(konusma.next().value); // "Adınız nedir?"
console.log(konusma.next("Tarık").value); // "Merhaba Tarık! Yaşınız?"
console.log(konusma.next(30).value); // "Tarık, 30 yaşındasınız. Hoş geldiniz!"
// return() ve throw() ile kontrol
function* kontrollu() {
try {
yield 1;
yield 2;
yield 3;
} catch (hata) {
console.log("Hata yakalandı:", hata.message);
} finally {
console.log("Temizlik yapıldı");
}
}
const g = kontrollu();
g.next(); // { value: 1, done: false }
g.throw(new Error("Durdur!")); // "Hata yakalandı: Durdur!" → "Temizlik yapıldı"
4. Pratik Kullanım Senaryoları
// 1. ID generator
function* idUretici(onEk = "ID") {
let sayac = 1;
while (true) {
yield `${onEk}-${String(sayac++).padStart(6, "0")}`;
}
}
const kullaniciId = idUretici("USR");
console.log(kullaniciId.next().value); // "USR-000001"
console.log(kullaniciId.next().value); // "USR-000002"
// 2. Sayfalı veri iterasyonu
function* sayfalıGetir(url, sayfaBoyutu = 20) {
let sayfa = 1;
let devam = true;
while (devam) {
const yanit = yield fetch(`${url}?sayfa=${sayfa}&limit=${sayfaBoyutu}`)
.then(r => r.json());
sayfa++;
devam = yanit && yanit.length === sayfaBoyutu;
}
}
// 3. Durum makinesi (State machine)
function* trafıkLambasi() {
while (true) {
yield "kırmızı";
yield "yeşil";
yield "sarı";
}
}
const lamba = trafıkLambasi();
console.log(lamba.next().value); // "kırmızı"
console.log(lamba.next().value); // "yeşil"
console.log(lamba.next().value); // "sarı"
console.log(lamba.next().value); // "kırmızı" (tekrar)
// 4. Ağaç yapısı traversal (DFS)
function* derinOncelik(dugum) {
yield dugum.deger;
if (dugum.cocuklar) {
for (const cocuk of dugum.cocuklar) {
yield* derinOncelik(cocuk);
}
}
}
const agac = {
deger: 1,
cocuklar: [
{ deger: 2, cocuklar: [{ deger: 4 }, { deger: 5 }] },
{ deger: 3, cocuklar: [{ deger: 6 }] }
]
};
console.log([...derinOncelik(agac)]); // [1, 2, 4, 5, 3, 6]
async/await aslında generator'lar üzerine inşa edilmiştir. Babel gibi transpiler'lar, async fonksiyonları generator tabanlı koda dönüştürür. Redux-Saga gibi kütüphaneler hâlâ generator'ları aktif olarak kullanır.
Sonuç
Generator ve iterator'lar, JavaScript'te tembel değerlendirme, sonsuz diziler, özel iterasyon mantığı ve durum makineleri gibi güçlü pattern'leri mümkün kılar. Iterator protokolünü ve generator sözdizimini anlamak, hem daha verimli kod yazmanızı hem de async/await gibi dil özelliklerinin perde arkasını kavramanızı sağlar.