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()
Wennnext()
aufgerufen wird, wird die Ausführung der Generator-Funktion vom letztenyield
-Ausdruck fortgesetzt oder vom Anfang, wenn es der erste Aufruf ist.next()
gibt immer 2 Werte zurück.value
Der Wert, der durch denyield
-Ausdruck oder den Rückgabewert der Generator-Funktion erzeugt wird. Dies ist der Hauptwert, den der Generator zurückgibt.done
Ein Boolean-Wert (true
oderfalse
), der angibt, ob der Generator seine Ausführung abgeschlossen hat (true
) oder ob es noch weitere Werte zuyield
gibt (false
). Lass dich vom return in dem obigen Beispiel nicht irritieren. Bei einemyield 3;
würde ebenfalls in done eintrue
stehen, weil es das letzteyield
ist.
next(Value)
Es ist möglich, mitnext()
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:
- JavaScript (seit ECMAScript 2015/ES6, 2015)
- Python (seit Version 2.2, 2001)
- C# (seit Version 2.0, 2005)
- PHP (seit Version 5.5, 2013)
- Ruby (seit Version 1.9, 2007)
- Kotlin (seit Version 1.1 mit
sequences
, 2017) - Swift (seit Version 5.0 mit
AsyncSequence
, 2019) - TypeScript (seit Version 2.3, 2017)
- Lua (seit Version 5.0, 2003)
- Rust (seit Version 1.39 mit
async
/await
, 2019) - Scala (seit Version 2.8, 2010)
- Dart (seit Version 2.0, 2018)
- Groovy (seit Version 2.5, 2018)
- CoffeeScript (seit Version 2.0, 2017)
- Julia (seit Version 0.5, 2016)
- Nim (seit Version 0.11.2, 2015)
- Crystal (seit Version 0.24.1, 2017)
- C++ (seit C++20, 2020)