← Kursa Dön
📄 Text · 18 min

Iterator ve Generator

Neden Bu Konu Önemli?

Diyelim ki bir kitaplığın var ve raflarında binlerce kitap var. Bir arkadaşın "Bana kitapları göster" dediğinde, tüm kitapları bir anda kucağına yığmak yerine, teker teker uzatırsın. O bir kitaba bakar, "devam" der, sen bir sonrakini verirsin. İstediği zaman "yeter" der ve durursun. Tüm kitapları taşımak yerine, teker teker ve talep üzerine verirsin.

İşte Iterator ve Generator tam olarak bu mekanizmayı JavaScript'e kazandırır. Verileri bir anda bellekte tutmak yerine, tembel değerlendirme (lazy evaluation) ile sadece istendiğinde üretirsin. Bu, bellek verimliliği, sonsuz diziler ve veri akışı (streaming) gibi güçlü konseptlerin kapısını açar.

Bu kavramlar ilk bakışta soyut görünebilir ama gerçek dünyada çok kullanılır: for...of döngüsü, spread operatörü, destructuring — hepsi arkada iterator protokolünü kullanır.


Iterable ve Iterator Protokolü

JavaScript'te bir nesnenin döngüye alınabilmesi (iterable olması) için iterable protokolünü uygulaması gerekir. Bu protokol basittir: nesnenin Symbol.iterator adında bir metodu olmalı ve bu metot bir iterator nesnesi döndürmeli.

Iterator Nedir?

Iterator, next() metodu olan bir nesnedir. Her next() çağrısı şu formatta bir nesne döner:

{ value: <herhangi bir değer>, done: <boolean> }
  • value: O adımın değeri

  • done: Daha fazla değer var mı? (true = bitti, false = devam)

// Manuel iterator kullanımı — for...of'un arkasında ne çalıştığını görelim
const dizi = ["elma", "armut", "muz"];

// Symbol.iterator metodunu çağırarak iterator al
const iterator = dizi[Symbol.iterator]();

console.log(iterator.next()); // { value: "elma",  done: false }
console.log(iterator.next()); // { value: "armut", done: false }
console.log(iterator.next()); // { value: "muz",   done: false }
console.log(iterator.next()); // { value: undefined, done: true }
// ↑ done: true — dizi bitti

for...of Nasıl Çalışır?

for...of döngüsü arkada iterator protokolünü kullanır:

const meyveler = ["elma", "armut", "muz"];

// Bu:
for (const meyve of meyveler) {
  console.log(meyve);
}

// Aslında şununla eşdeğer:
const iter = meyveler[Symbol.iterator]();
let sonuc = iter.next();

while (!sonuc.done) {
  const meyve = sonuc.value;
  console.log(meyve);
  sonuc = iter.next();
}

Yerleşik Iterable'lar

JavaScript'te birçok yerleşik veri tipi iterable'dır:

// Array
for (const item of [1, 2, 3]) { /* ... */ }

// String — her karakter bir adım
for (const char of "Merhaba") {
  console.log(char); // M, e, r, h, a, b, a
}

// Map
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, value] of map) {
  console.log(`${key}: ${value}`);
}

// Set
const set = new Set([1, 2, 3, 2, 1]); // Tekrarlar kaldırılır
for (const item of set) {
  console.log(item); // 1, 2, 3
}

// NodeList (DOM)
for (const element of document.querySelectorAll("p")) {
  element.style.color = "red";
}

// arguments nesnesi
function ornek() {
  for (const arg of arguments) {
    console.log(arg);
  }
}

⚠️ Dikkat: Düz nesneler (plain objects) iterable değildir! for...of ile döngüye alamazsın.

const nesne = { a: 1, b: 2, c: 3 };

// ❌ TypeError: nesne is not iterable
// for (const item of nesne) { }

// ✅ Object.entries ile iterable yap
for (const [key, value] of Object.entries(nesne)) {
  console.log(`${key}: ${value}`);
}

// ✅ veya for...in kullan (key'ler üzerinde döner)
for (const key in nesne) {
  console.log(`${key}: ${nesne[key]}`);
}

Kendi Iterator'ünü Yazmak

Herhangi bir nesneyi iterable yapabilirsin. Tek gereken Symbol.iterator metodunu implement etmek.

// Bir sayı aralığı oluşturan iterable nesne
const aralik = {
  baslangic: 1,
  bitis: 5,
  
  // Symbol.iterator metodu — bu nesneyi iterable yapar
  [Symbol.iterator]() {
    let current = this.baslangic;
    const last = this.bitis;
    
    // Iterator nesnesi döndür
    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};

// Artık for...of ile kullanılabilir!
for (const sayi of aralik) {
  console.log(sayi); // 1, 2, 3, 4, 5
}

// Spread ile dizi yapılabilir!
const dizi = [...aralik]; // [1, 2, 3, 4, 5]

// Destructuring çalışır!
const [ilk, ikinci, ...kalan] = aralik; // 1, 2, [3, 4, 5]

Daha Karmaşık Örnek: Linked List

// Bağlı liste (linked list) — kendi veri yapın
class Node {
  constructor(value, next = null) {
    this.value = value;
    this.next = next;
  }
}

class LinkedList {
  constructor() {
    this.head = null;
    this.size = 0;
  }
  
  add(value) {
    const node = new Node(value);
    if (!this.head) {
      this.head = node;
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = node;
    }
    this.size++;
  }
  
  // İterable protokolü — for...of ile kullanılabilir
  [Symbol.iterator]() {
    let current = this.head;
    
    return {
      next() {
        if (current) {
          const value = current.value;
          current = current.next;
          return { value, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  }
}

// Kullanım
const liste = new LinkedList();
liste.add("A");
liste.add("B");
liste.add("C");

for (const item of liste) {
  console.log(item); // A, B, C
}

console.log([...liste]); // ["A", "B", "C"]

Generator Fonksiyonlar

Iterator yazmak güçlü ama biraz fazla boilerplate gerektiriyor. Generator fonksiyonlar, iterator oluşturmayı çok daha kolay hale getirir.

Generator fonksiyonlar function* sözdizimi ile tanımlanır ve yield keyword'ü ile değer üretir.

Temel Syntax

// Generator fonksiyon — function* ile tanımlanır
function* sayiUretici() {
  yield 1;
  yield 2;
  yield 3;
}

// Generator çağrıldığında FONKSİYON ÇALIŞMAZ!
// Bir iterator nesnesi döner
const gen = sayiUretici();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

yield: Duraklat ve Değer Ver

yield keyword'ü fonksiyonun çalışmasını duraklatır ve bir değer döndürür. Sonraki next() çağrısında kaldığı yerden devam eder.

function* selamlamaUretici() {
  console.log("Başladı");
  yield "Merhaba";
  
  console.log("Devam ediyor");
  yield "Nasılsın?";
  
  console.log("Bitiyor");
  return "Hoşçakal"; // return ile biter — done: true olur
}

const gen = selamlamaUretici();

console.log(gen.next());
// Konsol: "Başladı"
// Dönen: { value: "Merhaba", done: false }

console.log(gen.next());
// Konsol: "Devam ediyor"
// Dönen: { value: "Nasılsın?", done: false }

console.log(gen.next());
// Konsol: "Bitiyor"
// Dönen: { value: "Hoşçakal", done: true }
// ↑ return ile bitti — done: true
// ⚠️ for...of return değerini GÖSTERMez!

💡 İpucu: yield ile return farkı: yield duraklatır ve devam edebilir. return generator'ı bitirir. for...of döngüsü done: true olanı atlar, bu yüzden return değeri döngüde görünmez.

Generator'lar Iterable'dır

Generator fonksiyonlar otomatik olarak iterable'dır — for...of, spread, destructuring ile kullanılabilir:

function* fibonacci() {
  let a = 0, b = 1;
  
  while (true) { // Sonsuz döngü — ama tembel!
    yield a;
    [a, b] = [b, a + b];
  }
}

// for...of ile ilk 10 Fibonacci sayısı
let sayac = 0;
for (const sayi of fibonacci()) {
  if (sayac >= 10) break; // Sonsuz döngüyü break ile durdur
  console.log(sayi);
  sayac++;
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

// veya yardımcı fonksiyonla
function* ilkN(generator, n) {
  let count = 0;
  for (const value of generator) {
    if (count >= n) return;
    yield value;
    count++;
  }
}

console.log([...ilkN(fibonacci(), 10)]);
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Lazy Evaluation (Tembel Değerlendirme)

Generator'ların en büyük avantajı tembel değerlendirmedir. Değerler ancak istendiğinde hesaplanır — önceden tamamı belleğe yüklenmez.

Eager vs Lazy Karşılaştırması

// ❌ EAGER (istekli) — tüm veriyi önceden oluştur
function ciftSayilar(n) {
  const sonuc = [];
  for (let i = 0; i < n; i++) {
    sonuc.push(i * 2); // Tüm diziyi bellekte tut
  }
  return sonuc;
}

const tumu = ciftSayilar(1_000_000); // 1 milyon elemanlı dizi — çok bellek!
console.log(tumu[0]); // 0 — ama 999.999 eleman boşuna oluşturuldu

// ✅ LAZY (tembel) — sadece isteneni üret
function* ciftSayilarLazy(n) {
  for (let i = 0; i < n; i++) {
    yield i * 2; // Her seferinde sadece BİR değer
  }
}

const lazyGen = ciftSayilarLazy(1_000_000);
console.log(lazyGen.next().value); // 0 — sadece bu hesaplandı
console.log(lazyGen.next().value); // 2 — sadece bu hesaplandı
// 999.998 eleman hiç oluşturulmadı — bellek tasarrufu!

Sonsuz Diziler

Lazy evaluation ile sonsuz diziler oluşturabilirsin — çünkü tüm diziyi bellekte tutmuyorsun:

// Sonsuz ID üretici
function* idUretici(baslangic = 1) {
  let id = baslangic;
  while (true) {
    yield id++;
  }
}

const idGen = idUretici();
console.log(idGen.next().value); // 1
console.log(idGen.next().value); // 2
console.log(idGen.next().value); // 3
// Sonsuza kadar devam edebilir — ama sadece isteneni üretir

// Sonsuz rastgele sayı dizisi
function* rastgeleDizi() {
  while (true) {
    yield Math.random();
  }
}

// İlk 5 rastgele sayı
const rastgele5 = [...ilkN(rastgeleDizi(), 5)];
console.log(rastgele5); // [0.482, 0.193, 0.871, 0.556, 0.329]

Generator ile Veri Gönderme

next() metoduna değer geçerek generator'a veri gönderebilirsin. Gönderilen değer, yield ifadesinin dönüş değeri olur.

function* diyalog() {
  const isim = yield "Adınız nedir?";
  const yas = yield `Merhaba ${isim}! Kaç yaşındasınız?`;
  return `${isim}, ${yas} yaşında. Kayıt tamamlandı!`;
}

const gen = diyalog();

// İlk next() — generator'ı başlatır, yield'deki değeri alır
console.log(gen.next());        
// { value: "Adınız nedir?", done: false }

// İkinci next("Ahmet") — "Ahmet" isim'e atanır
console.log(gen.next("Ahmet"));
// { value: "Merhaba Ahmet! Kaç yaşındasınız?", done: false }

// Üçüncü next(28) — 28 yas'a atanır
console.log(gen.next(28));
// { value: "Ahmet, 28 yaşında. Kayıt tamamlandı!", done: true }

⚠️ Dikkat: İlk next() çağrısına değer göndermek anlamsızdır çünkü henüz bekleyen bir yield yok. Generator henüz ilk yield'e ulaşmamıştır.

Generator'a Hata Fırlatma

function* guvenliGenerator() {
  try {
    const deger = yield "Bir değer girin";
    console.log("Alınan:", deger);
    yield "Bir değer daha girin";
  } catch (err) {
    console.error("Generator içinde hata:", err.message);
    yield "Hata kurtarma değeri";
  }
}

const gen = guvenliGenerator();
console.log(gen.next());           // { value: "Bir değer girin", done: false }
console.log(gen.throw(new Error("Test hatası")));
// "Generator içinde hata: Test hatası"
// { value: "Hata kurtarma değeri", done: false }

yield* — Generator Delegasyonu

yield* ile bir generator, başka bir iterable'a veya generator'a delege edebilir:

// yield* — başka bir iterable'ın tüm değerlerini yield et
function* harfler() {
  yield* "ABC"; // String iterable — her karakteri yield et
  yield* [1, 2, 3]; // Dizi iterable
}

console.log([...harfler()]); // ["A", "B", "C", 1, 2, 3]

// Generator delegasyonu
function* iller() {
  yield "İstanbul";
  yield "Ankara";
}

function* ilceler() {
  yield "Kadıköy";
  yield "Beşiktaş";
}

function* tumKonumlar() {
  yield* iller();   // iller generator'ına delege et
  yield* ilceler(); // ilceler generator'ına delege et
  yield "Ek konum";
}

console.log([...tumKonumlar()]);
// ["İstanbul", "Ankara", "Kadıköy", "Beşiktaş", "Ek konum"]

Ağaç Yapısını Gezme (Tree Traversal)

// Binary tree — yield* ile recursive traversal
class TreeNode {
  constructor(value, left = null, right = null) {
    this.value = value;
    this.left = left;
    this.right = right;
  }
  
  // In-order traversal generator
  *[Symbol.iterator]() {
    if (this.left) yield* this.left;   // Sol alt ağacı gez
    yield this.value;                   // Kendini ver
    if (this.right) yield* this.right; // Sağ alt ağacı gez
  }
}

// Ağaç oluştur
//       4
//      / \
//     2   6
//    / \ / \
//   1  3 5  7
const tree = new TreeNode(
  4,
  new TreeNode(2, new TreeNode(1), new TreeNode(3)),
  new TreeNode(6, new TreeNode(5), new TreeNode(7))
);

// for...of ile sıralı gezme
for (const val of tree) {
  console.log(val); // 1, 2, 3, 4, 5, 6, 7 (sıralı!)
}

console.log([...tree]); // [1, 2, 3, 4, 5, 6, 7]

Pratik Kullanım Alanları

Sayfalı Veri Çekme

// API'den sayfa sayfa veri çekme
async function* sayfaliVeriCek(baseUrl, sayfaBoyutu = 10) {
  let sayfa = 1;
  let devamVar = true;
  
  while (devamVar) {
    const response = await fetch(
      `${baseUrl}?page=${sayfa}&limit=${sayfaBoyutu}`
    );
    const data = await response.json();
    
    yield data.items;
    
    devamVar = data.items.length === sayfaBoyutu; // Son sayfa değilse devam
    sayfa++;
  }
}

// for await...of ile kullanım
async function tumVerileriGetir() {
  const tumOgeler = [];
  
  for await (const sayfa of sayfaliVeriCek("/api/products")) {
    tumOgeler.push(...sayfa);
    console.log(`${tumOgeler.length} ürün yüklendi...`);
  }
  
  return tumOgeler;
}

Benzersiz ID Üretici

// UUID benzeri benzersiz ID üretici
function* benzersizId(prefix = "") {
  let sayac = 0;
  
  while (true) {
    const zaman = Date.now().toString(36);
    const rastgele = Math.random().toString(36).substring(2, 8);
    yield `${prefix}${zaman}-${rastgele}-${sayac++}`;
  }
}

const idGen = benzersizId("user_");
console.log(idGen.next().value); // "user_m1abc23-x7kf92-0"
console.log(idGen.next().value); // "user_m1abc23-p3mn56-1"
console.log(idGen.next().value); // "user_m1abc24-q8rs12-2"

Pipeline (Boru Hattı) İşleme

// Fonksiyonel pipeline — generator zincirleme
function* filtrele(iterable, kosul) {
  for (const item of iterable) {
    if (kosul(item)) yield item;
  }
}

function* donustur(iterable, fn) {
  for (const item of iterable) {
    yield fn(item);
  }
}

function* sinirla(iterable, n) {
  let count = 0;
  for (const item of iterable) {
    if (count >= n) return;
    yield item;
    count++;
  }
}

// Pipeline: 1'den sonsuza → çift olanlar → karesini al → ilk 5
const sonuc = sinirla(
  donustur(
    filtrele(
      idUretici(1),     // 1, 2, 3, 4, ...
      (n) => n % 2 === 0 // Çift sayılar: 2, 4, 6, 8, ...
    ),
    (n) => n * n          // Kareler: 4, 16, 36, 64, ...
  ),
  5                       // İlk 5: 4, 16, 36, 64, 100
);

console.log([...sonuc]); // [4, 16, 36, 64, 100]
// Sonsuz diziden, bellek kullanmadan, tembel olarak 5 sonuç!

Yaygın Hatalar ve Tuzaklar

Hata 1: Generator'ı Yeniden Kullanmak

// ❌ Generator iterator TEK KULLANIMLIK
function* sayilar() {
  yield 1; yield 2; yield 3;
}

const gen = sayilar();
console.log([...gen]); // [1, 2, 3]
console.log([...gen]); // [] — BOŞ! Generator tükendi

// ✅ Her kullanımda yeni generator oluştur
console.log([...sayilar()]); // [1, 2, 3]
console.log([...sayilar()]); // [1, 2, 3]

Hata 2: Arrow Function Generator Yok

// ❌ Arrow function ile generator tanımlanamaz
// const gen = *() => { yield 1; }; // SyntaxError!
// const gen = () => * { yield 1; }; // SyntaxError!

// ✅ function keyword zorunlu
const gen = function* () { yield 1; };

// ✅ Metot syntax'ı kullanılabilir
const nesne = {
  *uret() { yield 1; yield 2; },
};

Hata 3: return vs yield Karışıklığı

function* ornek() {
  yield 1;
  yield 2;
  return 3; // done: true olur
  yield 4;  // BURAYA ASLA ULAŞILMAZ!
}

// for...of return değerini GÖSTERMEZ
for (const val of ornek()) {
  console.log(val); // 1, 2 — 3 YOK! (done: true atlanır)
}

// next() ile return değerini görebilirsin
const gen = ornek();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true } ← burada

Özet

Bu derste Iterator ve Generator kavramlarını öğrendik:

  • Iterator protokolü: next() metodu olan, { value, done } döndüren nesneler. for...of, spread, destructuring hep bunu kullanır.

  • Symbol.iterator: Bir nesneyi iterable yapmak için bu metodu tanımlamak gerekir. Diziler, string'ler, Map, Set zaten iterable'dır.

  • Generator fonksiyonlar (function*): yield ile değer üreten, her yield'de duraklatılan özel fonksiyonlar. Iterator yazmayı dramatik ölçüde kolaylaştırır.

  • Lazy evaluation: Değerler sadece istendiğinde üretilir — sonsuz diziler, büyük veri kümeleri bellekte yer kaplamadan işlenebilir.

  • yield*: Bir generator'dan başka bir iterable'a delege etme — ağaç yapıları, iç içe koleksiyonlar için ideal.

  • Async generator (async function* + for await...of): Asenkron veri akışları — sayfalı API'ler, stream'ler için mükemmel.

Bir sonraki derste Proxy ve Reflect ile metaprogramlama dünyasına gireceğiz.