Geschichte

Generators wurden in Python populär und dann in JavaScript eingeführt.

Best Practice

Generator Funktionen haben ihren Einsatzbereich beim Verarbeiten von Datenströmen, großer Dateien und bei der Implementierung von Integrations-Protokolle.

Statt alle Werte auf einmal zu berechnen und im Speicher zu halten, erzeugt sie Werte bei Bedarf. Das spart Speicher und kann die Laufzeit verbessern.

Beispiel einfache Generator Functions:

function* beispielGenerator() {
  yield 1;
  yield 2;
  return 3; // Ein Generator kann auch einen Rückgabewert haben, der beim letzten Aufruf von next() zurückgegeben wird
}

const generator = beispielGenerator();

console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: true }
console.log(generator.next()); // { value: undefined, done: true }
  • function* Das Sternzeichen ist das Erkennungsmerkmal einer Generator-Funktion. Ohne würde es eine Fehlermeldung geben.
  • yield wird ausschließlich in Generator-Funktionen verwendet. Es dient dazu, den Wert aus der Funktion zurückzugeben.
  • next() Wenn next() aufgerufen wird, wird die Ausführung der Generator-Funktion vom letzten yield-Ausdruck fortgesetzt oder vom Anfang, wenn es der erste Aufruf ist. next() gibt immer 2 Werte zurück.
    • value Der Wert, der durch den yield-Ausdruck oder den Rückgabewert der Generator-Funktion erzeugt wird. Dies ist der Hauptwert, den der Generator zurückgibt.
    • done Ein Boolean-Wert (true oder false), der angibt, ob der Generator seine Ausführung abgeschlossen hat (true) oder ob es noch weitere Werte zu yield gibt (false). Lass dich vom return in dem obigen Beispiel nicht irritieren. Bei einem yield 3; würde ebenfalls in done ein true stehen, weil es das letzte yield ist.
  • next(Value) Es ist möglich, mit next() einen Wert an die Generator-Funktion zu übergeben.

Edge-Case Beispiel:

function* permute<T>(arr: T[], n: number = arr.length): Generator<T[]> {
    if (n <= 1) {
        yield arr.slice();
    } else {
        for (let i = 0; i < n; i++) {
            yield* permute(arr, n - 1);
            const j = n % 2 === 0 ? i : 0;
            [arr[n - 1], arr[j]] = [arr[j], arr[n - 1]];
        }
    }
}

// Verwendung:
const iterator = permute([1, 2, 3]);
for (const perm of iterator) {
    console.log(perm); 
    // [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]    
}

Eine Permutation ist eine Anordnung oder Umstellung von Elementen in einer bestimmten Reihenfolge. Wenn du eine Liste von Elementen hast, gibt es verschiedene Möglichkeiten, die Elemente neu anzuordnen, und jede mögliche Anordnung wird als Permutation bezeichnet. Wie so etwas aussieht, kannst du in dem Kommentar im Beispiel sehen.

In diesem Beispiel haben wir eine Generator-Funktion mit generischen Datentypen <T>. Es wird eine Konstante iterator deklariert, um die Generator-Funktion permute als Objekt und mit den Parametern [1, 2, 3] nutzen zu können. In der for...of Schleife, ist die Konstante perm, die für eine Permutation steht. Durch das yield haben wir immer einen Rückgabewert, der über console.log() ausgeben wird.

Proof of Concept – API-Generator Beispiel

Hier ein Beispiel, wie du eine API-Anfrage (Application Programming Interface, Programmierschnittstelle) mit einer Generator-Funktion kombinieren kannst.

type User = {
    id: number;
    name: string;
};

async function fetchUsers(page: number): Promise<User[]> {
    const response = await fetch(`https://api.example.com/users?page=${page}`);
    if (!response.ok) {
        throw new Error('Netzwerk-Antwort war nicht okay');
    }
    const data = await response.json();
    return data.users;
}

async function* userGenerator(): AsyncGenerator<User[], void, unknown> {
    let page = 1;
    while (true) {
        const users = await fetchUsers(page);
        if (users.length === 0) {
            break;
        }
        yield users;
        page++;
    }
}

async function processUsers() {
    const generator = userGenerator();
    for await (const users of generator) {
        users.forEach(user => console.log(`User ID: ${user.id}, Name: ${user.name}`));
    }
}

processUsers().catch(error => console.error('Fehler:', error));

Am Anfang ist eine Typ-Alias-Deklaration in der, der Typ User definiert wurde, um ein Objekt mit den Eigenschaften id vom Datentyp number und name vom Datentyp string haben.

An den Funktionen fetchUsers() und userGenerator() sehen wir, dass Users als generischer Array Datentyp besteht.

Am Ende des Source-Codes rufen wir die asynchrone processUsers() mit der catch() Methode auf. Damit werden Fehler bei der Verbindung ausgegeben.

In der processUsers() wird in der Konstante generator ein Objekt von der Funktion userGenerator() erstellt.

In der for await Schleife wird durch die Konstante generator die userGenerator() aufgerufen, der die asynchrone function fetchUsers() aufruft, um den API-Link aufzurufen. Der Rückgabewert von fetchUsers() wird in yield gespeichert und durch page++ wiederholt sich das ganze bis ein Link 404 meldet oder keinen Inhalt hat.

In der Funktion processUsers() wird durch users.forEach() das Array durch console.log() ausgegeben.

Generative-Funktion als Methode in Klassen

Wenn du eine Generative-Funktion in einer Klasse brauchst, musst das Sternzeichen am Anfang des Bezeichners stehen.

class NumberGenerator {
    *generateNumbers(): IterableIterator<number> {
        let i = 0;
        while (i < 5) {
            yield i++;
        }
    }
}

const generator = new NumberGenerator();
const numbers = generator.generateNumbers();

for (const num of numbers) {
    console.log(num); // Ausgabe: 0, 1, 2, 3, 4
}

Weitere Informationen

Hier ist eine Liste von Programmiersprachen, die explizit Generator-Funktionen unterstützen, einschließlich der Version oder des Jahres, ab dem diese Funktion verfügbar war:

  1. JavaScript (seit ECMAScript 2015/ES6, 2015)
  2. Python (seit Version 2.2, 2001)
  3. C# (seit Version 2.0, 2005)
  4. PHP (seit Version 5.5, 2013)
  5. Ruby (seit Version 1.9, 2007)
  6. Kotlin (seit Version 1.1 mit sequences, 2017)
  7. Swift (seit Version 5.0 mit AsyncSequence, 2019)
  8. TypeScript (seit Version 2.3, 2017)
  9. Lua (seit Version 5.0, 2003)
  10. Rust (seit Version 1.39 mit async/await, 2019)
  11. Scala (seit Version 2.8, 2010)
  12. Dart (seit Version 2.0, 2018)
  13. Groovy (seit Version 2.5, 2018)
  14. CoffeeScript (seit Version 2.0, 2017)
  15. Julia (seit Version 0.5, 2016)
  16. Nim (seit Version 0.11.2, 2015)
  17. Crystal (seit Version 0.24.1, 2017)
  18. C++ (seit C++20, 2020)

Diese Seite verwendet Cookies, um die Nutzerfreundlichkeit zu verbessern. Mit der weiteren Verwendung stimmst du dem zu.

Datenschutzerklärung