JavaScript'te iterator, next() metodu her çağrıldığında { value, done } biçiminde bir sonuç nesnesi döndüren nesnedir. Iterable ise [Symbol.iterator]() metoduyla bir iterator üretebilen nesnedir ve for...of, spread (...) ve destructuring bu protokolü kullanır. Generator fonksiyonları (function*) ise yield ile değer üreterek iterator yazmayı çok kolaylaştırır.

Bu yazıda iterator ve iterable protokollerini, kendi iterable nesnenizi yazmayı, generator'ların çalışma biçimini, for...of döngüsünü ve kısaca async iterator ile for await...of yapısını Node.js 24'te çalıştırılmış örneklerle anlatıyoruz.

Iterator protokolü

Iterator protokolü

Bir nesnenin iterator olması için next() metodu yeterlidir. Bu metot value (sıradaki değer) ve done (dizi bitti mi) özellikleri olan bir nesne döndürür. done: true olduğunda value varsa iterator'ın dönüş değeri sayılır. Bir dizinin iterator'ını elle tüketerek protokolü görebilirsiniz:

javascript
const dizi = ['a', 'b'];
const it = dizi[Symbol.iterator]();
console.log(it.next()); // { value: 'a', done: false }
console.log(it.next()); // { value: 'b', done: false }
console.log(it.next()); // { value: undefined, done: true }

Iterator isteğe bağlı olarak return(value) ve throw(exception) metotlarını da tanımlayabilir. return(), çağıranın artık değer istemediğini bildirir ve iterator'a temizlik yapma fırsatı verir. MDN, next() nesne olmayan bir değer döndürürse for...of gibi dil özelliklerinin TypeError fırlattığını belirtir.

Iterable protokolü ve kendi iterable nesneniz

Bir nesne, Symbol.iterator anahtarlı ve argüman almayan bir metot tanımladığında iterable olur. Bu metot her çağrıldığında bir iterator döndürmelidir. Defalarca gezilebilecek bir nesne her seferinde yeni bir iterator döndürür; yalnızca bir kez gezilebilen nesneler (generator'lar gibi) ise genellikle this döndürür.

javascript
function aralikIteratoru(bas, son) {
  let simdiki = bas;
  return {
    next() {
      if (simdiki < son) return { value: simdiki++, done: false };
      return { value: undefined, done: true };
    },
    [Symbol.iterator]() { return this; },
  };
}

for (const n of aralikIteratoru(1, 4)) console.log(n);
// 1
// 2
// 3

const aralik = {
  bas: 1,
  son: 4,
  [Symbol.iterator]() { return aralikIteratoru(this.bas, this.son); },
};
console.log([...aralik], [...aralik]); // [ 1, 2, 3 ] [ 1, 2, 3 ]

Symbol.iterator gibi "iyi bilinen" sembollerin ne olduğunu Symbol yazımızda anlattık. Sade nesneler ({}) varsayılan olarak iterable değildir; for...of ile gezmeye çalışırsanız "is not iterable" hatası alırsınız.

Generator fonksiyonları: function* ve yield

Yukarıdaki gibi durumu elle tutmak hataya açıktır. Generator fonksiyonları aynı işi tek bir fonksiyonla yapar. function* ile tanımlanan bir fonksiyon çağrıldığında gövdesi hemen çalışmaz; bunun yerine bir Generator nesnesi döner. Her next() çağrısında gövde bir sonraki yield'e kadar çalışır ve orada durur. return ile verilen değer done: true olan sonuçta görünür.

javascript
function* sayac() {
  console.log('gövde başladı');
  yield 1;
  yield 2;
  return 'bitti';
}

const g = sayac();
console.log('henüz çalışmadı');
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 'bitti', done: true }
console.log(g.next()); // { value: undefined, done: true }
// Çıktı sırası: henüz çalışmadı, gövde başladı, ardından dört sonuç nesnesi

Dikkat: for...of ve spread, done: true ile gelen dönüş değerini almaz; [...sayac()] sonucu [ 1, 2 ] olur. Generator nesnesi hem iterator hem iterable'dır ve [Symbol.iterator]() metodu kendisini döndürür; bu yüzden yalnızca bir kez gezilebilir. MDN'in yürütme modeli sayfası, generator'ın yürütme bağlamının yield'de askıya alınıp yığından çıktığını, next() ile geri geldiğini anlatır; bu konuya event loop yazımızda değindik.

Sınıflarda generator ile iterable yazmak

MDN, [Symbol.iterator]() metodunun sıradan bir fonksiyon olabileceği gibi bir generator da olabileceğini belirtir. Bir sınıfta metodu *[Symbol.iterator]() biçiminde yazarsanız her for...of döngüsü yeni bir generator alır; böylece nesne defalarca gezilebilir ve iç durumunuz (örneğin özel bir alan) dışarıya açılmaz:

javascript
class Raf {
  #kitaplar = [];
  ekle(...k) {
    this.#kitaplar.push(...k);
    return this;
  }
  *[Symbol.iterator]() {
    for (const k of this.#kitaplar) yield k.toUpperCase();
  }
}

const raf = new Raf().ekle('sefiller', 'suç ve ceza');
for (const k of raf) console.log(k);
// SEFILLER
// SUÇ VE CEZA
console.log(Array.from(raf).length); // 2

Burada Array.from da iterable kabul eden yerleşik API'lerden biridir. Sınıf sözdizimi ve özel alanlar için class yazımıza göz atabilirsiniz.

Sonsuz diziler ve iterator yardımcıları

Sonsuz diziler ve iterator yardımcıları

Generator'lar değerleri ihtiyaç anında ürettiği için sonsuz bir diziyi bile güvenle tanımlayabilirsiniz; yeter ki yalnızca ihtiyacınız kadarını tüketin. Yerleşik Iterator yardımcı metotları (take, filter, map, toArray vb.) ara dizi oluşturmadan bu tür iterator'larla çalışmanızı sağlar:

javascript
function* idUret() {
  let id = 1;
  while (true) yield id++;
}

const ids = idUret();
console.log(ids.next().value, ids.next().value, ids.next().value); // 1 2 3

console.log(idUret().take(5).filter((n) => n % 2 === 1).toArray()); // [ 1, 3, 5 ]

MDN, bu metotların "iterable" değil "iterator" yardımcıları olduğunu vurgular: dizide değil, dizinin values() gibi bir metotla verdiği iterator üzerinde çağrılırlar. Hedef ortamınızın desteği için MDN uyumluluk tablosuna bakın.

next(), return() ve throw() ile generator'ı yönlendirmek

next(deger) ile verdiğiniz değer, generator içinde o an bekleyen yield ifadesinin sonucu olur. İlk next() çağrısına verilen değer her zaman yok sayılır, çünkü henüz bekleyen bir yield yoktur.

javascript
function* sohbet() {
  const ad = yield 'Adınız?';
  const sehir = yield `Merhaba ${ad}, şehriniz?`;
  return `${ad} / ${sehir}`;
}

const s = sohbet();
console.log(s.next('yok sayılır').value); // Adınız?
console.log(s.next('Elif').value); // Merhaba Elif, şehriniz?
console.log(s.next('İzmir')); // { value: 'Elif / İzmir', done: true }

return(deger) generator'ı o noktada bitirir; throw(hata) ise bekleyen yield yerinde bir hata fırlatılmış gibi davranır. Generator içinde try...finally varsa finally bloğu her iki durumda da çalışır. for...of döngüsünden break ile çıkmak da return() çağırır:

javascript
function* kaynak() {
  try {
    yield 1;
    yield 2;
    yield 3;
  } finally {
    console.log('temizlik yapıldı');
  }
}

const gen = kaynak();
for (const v of gen) {
  console.log(v);
  if (v === 1) break;
}
// 1
// temizlik yapıldı
for (const v of gen) console.log('ikinci döngü', v); // hiç çalışmaz

const g2 = kaynak();
g2.next();
console.log(g2.return('erken'));
// temizlik yapıldı
// { value: 'erken', done: true }

const g3 = kaynak();
g3.next();
try {
  g3.throw(new Error('dışarıdan'));
} catch (e) {
  console.log('yakalandı:', e.message);
}
// temizlik yapıldı
// yakalandı: dışarıdan

Generator içinde yakalanmayan hata throw() çağrısının dışına taşar ve generator biter; sonraki next() çağrıları done: true döndürür. Hata yakalama kuralları için try/catch yazımıza bakın.

yield*, spread ve destructuring

yield*, başka bir iterable'ın tüm değerlerini sırayla üretir. Spread sözdizimi, dizi destructuring ve for...of da iterable bekleyen yapılardır:

javascript
function* ic() { yield 'b'; yield 'c'; }
function* dis() {
  yield 'a';
  yield* ic();
  yield* [1, 2];
}

console.log([...dis()]); // [ 'a', 'b', 'c', 1, 2 ]
const [ilk, ...kalan] = dis();
console.log(ilk, kalan); // a [ 'b', 'c', 1, 2 ]

try {
  for (const x of { a: 1 }) {}
} catch (e) {
  console.log(e.message); // {(intermediate value)} is not iterable
}

Spread ve destructuring ayrıntıları için destructuring ve spread yazımıza göz atabilirsiniz.

Yerleşik iterable'lar ve for...of ile for...in farkı

Yerleşik iterable'lar ve for...of ile for...in farkı: Neyi gezer?, Sade nesne {}, Dizi, Map, Set

MDN'e göre String, Array, TypedArray, Map ve Set yerleşik iterable'lardır. for...of bu nesnelerin değerlerini gezer. for...in ise bir nesnenin numaralandırılabilir özellik adlarını (string olarak) gezer; diziler için genellikle istediğiniz şey değildir.

javascript
const m = new Map([['a', 1], ['b', 2]]);
for (const [k, v] of m) console.log(k, v);
// a 1
// b 2

for (const harf of 'ağ') console.log(harf);
// a
// ğ

console.log(m.values().reduce((x, y) => x + y)); // 3

for (const i in ['x', 'y']) console.log(typeof i, i);
// string 0
// string 1
Özellikfor...offor...in
Neyi gezer?Iterable'ın değerleriniNesnenin numaralandırılabilir anahtarlarını
Sade nesne {}TypeError: is not iterableÇalışır
DiziElemanlarİndeksler (string)
Map, SetGirdiler / değerlerGenellikle hiçbir şey

Map ve Set için Map, Set ve WeakMap yazımıza bakabilirsiniz.

Kısaca: async iterator ve for await...of

Async iterator protokolü senkron olanla neredeyse aynıdır; tek fark next(), return() ve throw()'un sonuç nesnesini bir Promise içinde döndürmesidir. Async iterable nesneler [Symbol.asyncIterator]() metodunu tanımlar. async function* ile yazılan async generator'lar bu protokolü otomatik uygular ve for await...of ile gezilir. Sayfalı bir API'den veri çekmek tipik bir kullanımdır:

javascript
// sayfalar.mjs (top-level await için modül)
const bekle = (ms) => new Promise((r) => setTimeout(r, ms));

async function* sayfalar() {
  for (let sayfa = 1; sayfa <= 3; sayfa++) {
    await bekle(50);
    yield [`kayıt ${sayfa}a`, `kayıt ${sayfa}b`];
  }
}

for await (const kayitlar of sayfalar()) {
  console.log(kayitlar);
}
// [ 'kayıt 1a', 'kayıt 1b' ]
// [ 'kayıt 2a', 'kayıt 2b' ]
// [ 'kayıt 3a', 'kayıt 3b' ]

const tumu = await Array.fromAsync(sayfalar());
console.log(tumu.flat().length); // 6

for await...of yalnızca await kullanılabilen yerlerde, yani async fonksiyon içinde veya bir modülün üst seviyesinde çalışır. Senkron iterable'larla da çalışır ama MDN'e göre her adımda Promise açıldığı için daha yavaştır. Promise ve await temelleri için Promise ve async/await yazılarına bakın.

Sık yapılan hatalar

  • Generator'ı iki kez gezmeye çalışmak: generator tek kullanımlıktır; her seferinde fonksiyonu yeniden çağırın.
  • return değerini for...of ile beklemek: döngü done: true ile gelen değeri almaz.
  • İlk next()'e değer göndermek: bu değer yok sayılır.
  • Sonsuz generator'ı spread ile açmak: [...idUret()] asla bitmez; take() gibi bir sınır koyun.
  • Sade nesneyi for...of ile gezmek: Object.entries(obj) gibi bir iterable üretin.
  • for await...of'u async olmayan bağlamda kullanmak: sözdizimi hatası verir.

Sık Sorulan Sorular

Iterator ile iterable arasındaki fark nedir?

Iterator, next() metodu olan ve değerleri tek tek veren nesnedir. Iterable ise [Symbol.iterator]() çağrıldığında bir iterator üreten nesnedir. Bir nesne ikisi birden olabilir; generator nesneleri böyledir.

Generator ne zaman işe yarar?

Değerleri tembel (lazy) üretmek istediğinizde: hesaplaması pahalı veya sonsuz diziler, büyük veri üzerinde adım adım işlem ve özel iterable nesneler yazmak. MDN, generator'ların değerleri ihtiyaç anında hesapladığını vurgular.

Bir nesnenin iterator olup olmadığını anlayabilir miyim?

MDN'e göre next()'i gerçekten çağırıp sonucu doğrulamadan bunu güvenilir biçimde bilmek mümkün değildir. Bunun yerine nesnenin iterable olup olmadığını typeof obj[Symbol.iterator] === "function" ile kontrol edebilirsiniz.

async generator ile normal generator farkı nedir?

Async generator'da next() bir Promise döndürür ve gövde içinde await kullanılabilir. Gezmek için for await...of ya da Array.fromAsync gerekir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: JavaScript öğrenme rehberi · Symbol nedir · Map ve Set · Destructuring ve spread · async/await · Event loop