EMZETT.
Login

Kurz: Was for…of durchlaufen kann, nennt man iterierbar: Arrays, Strings, Maps, Sets und mehr. Du kannst auch eigene iterierbare Objekte bauen, am einfachsten mit Generatoren.

Teil des Kurses JavaScript

Kapitel 24 von 40 im Kurs JavaScript. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Was for...of durchlaufen kann, nennt man iterierbar: Arrays, Strings, Maps, Sets und mehr. Du kannst auch eigene iterierbare Objekte bauen, am einfachsten mit Generatoren.

Das Iterator-Protokoll

Ein Iterator ist ein Objekt mit einer Methode next(), die { value, done } liefert:

const it = [10, 20][Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());

Ausgabe:

{ value: 10, done: false }
{ value: 20, done: false }
{ value: undefined, done: true }

for...of, Spread (...), Destructuring und Array.from nutzen dieses Protokoll.

Generatoren: function*

Eine Generatorfunktion (function*) liefert Werte mit yield nacheinander und pausiert dazwischen:

function* zaehle(bis) {
  for (let i = 1; i <= bis; i++) {
    yield i;
  }
}
console.log([...zaehle(5)]);
for (const n of zaehle(3)) console.log("Wert", n);
 
const gen = zaehle(2);
console.log(gen.next(), gen.next(), gen.next());

Ausgabe:

[ 1, 2, 3, 4, 5 ]
Wert 1
Wert 2
Wert 3
{ value: 1, done: false } { value: 2, done: false } { value: undefined, done: true }

Der Code läuft erst weiter, wenn der nächste Wert angefordert wird (Lazy Evaluation). Dadurch sind auch unendliche Folgen möglich:

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}
function* nimm(n, quelle) {
  let i = 0;
  for (const x of quelle) {
    if (i++ >= n) return;
    yield x;
  }
}
console.log([...nimm(10, fibonacci())]);

Ausgabe:

[
  0, 1,  1,  2,  3,
  5, 8, 13, 21, 34
]

Eigene iterierbare Klassen

class Bereich {
  constructor(von, bis) { this.von = von; this.bis = bis; }
  *[Symbol.iterator]() {
    for (let i = this.von; i <= this.bis; i++) yield i;
  }
}
const b = new Bereich(3, 7);
console.log([...b], Math.max(...b));
for (const x of b) if (x % 2) console.log(x);

Ausgabe:

[ 3, 4, 5, 6, 7 ] 7
3
5
7

yield* und Werte in den Generator senden

function* inner() { yield "a"; yield "b"; }
function* outer() { yield 1; yield* inner(); yield 2; }
console.log([...outer()]);
 
function* dialog() {
  const name = yield "Wie heißt du?";
  const alter = yield `Hallo ${name}, wie alt bist du?`;
  return `${name} ist ${alter}`;
}
const d = dialog();
console.log(d.next().value);
console.log(d.next("Mia").value);
console.log(d.next(17));

Ausgabe:

[ 1, 'a', 'b', 2 ]
Wie heißt du?
Hallo Mia, wie alt bist du?
{ value: 'Mia ist 17', done: true }

Nützliche Muster

function* mapGen(quelle, f) { for (const x of quelle) yield f(x); }
function* filterGen(quelle, f) { for (const x of quelle) if (f(x)) yield x; }
function* zahlen() { let n = 1; while (true) yield n++; }
 
const ergebnis = [];
for (const x of filterGen(mapGen(zahlen(), n => n * n), n => n % 2)) {
  ergebnis.push(x);
  if (ergebnis.length === 5) break;
}
console.log(ergebnis);
console.log(Iterator.from(zahlen()).map(n => n * 2).take(3).toArray());

Ausgabe:

[ 1, 9, 25, 49, 81 ]
[ 2, 4, 6 ]

Asynchrone Iteratoren

Mit async function* und for await...of verarbeitest du Daten, die nach und nach eintreffen, etwa gelesene Dateistücke oder Seiten einer API:

async function* seiten() {
  for (let i = 1; i <= 3; i++) {
    await new Promise(r => setTimeout(r, 10));
    yield `Seite ${i}`;
  }
}
(async () => {
  for await (const s of seiten()) console.log(s);
})();

Ausgabe:

Seite 1
Seite 2
Seite 3

Merke

  • Iterierbare Objekte haben [Symbol.iterator](), Iteratoren next()
  • function* und yield erzeugen Werte nacheinander und pausieren dazwischen
  • Generatoren sind lazy und können unendlich sein
  • yield* delegiert an einen anderen Iterierbaren
  • async function* und for await...of für Datenströme

Übungsaufgabe

Schreibe einen Generator primzahlen(), der unendlich viele Primzahlen liefert, und gib die ersten zehn aus.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Module: import und export

Weiter: Asynchron: Callbacks und Promises

Alle Kapitel: JavaScript im Überblick