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ğeridone: 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 bittifor...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...ofile 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:
yieldilereturnfarkı:yieldduraklatır ve devam edebilir.returngenerator'ı bitirir.for...ofdöngüsüdone: trueolanı atlar, bu yüzdenreturndeğ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 biryieldyok. Generator henüz ilkyield'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*):yieldile değer üreten, heryield'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.
AI Asistan
Sorularını yanıtlamaya hazır