Das Verhalten der im vorherigen Artikel beschriebenen Generatoren ist nicht komplex, aber es ist definitiv überraschend und mag zunächst verwirrend erscheinen. Anstatt neue Konzepte zu lernen, werden wir jetzt ein interessantes Beispiel für die Verwendung von Generatoren betrachten.
Lassen Sie uns eine Funktion wie diese haben:
function maybeAddNumbers() {
const a = maybeGetNumberA();
const b = maybeGetNumberB();
return a + b;
}
Funktionen
maybeGetNumberA
und
maybeGetNumberB
Rückgabewerte, aber manchmal können sie
null
oder zurückgeben
undefined
. Dies wird durch das Wort "vielleicht" in ihren Namen belegt. Versuchen Sie in diesem Fall nicht, diese Werte (z. B. die Zahl und
null
) einzugeben
null
. Es ist beispielsweise besser, anzuhalten und zurückzukehren . Und zwar
null
nicht irgendein unvorhersehbarer Wert, der durch Hinzufügen von
null
/
undefined
mit einer Zahl oder einem anderen
null
/ erhalten wird
undefined
.
Sie müssen also überprüfen, ob die Zahlen tatsächlich definiert sind:
function maybeAddNumbers() {
const a = maybeGetNumberA();
const b = maybeGetNumberB();
if (a === null || a === undefined || b === null || b === undefined) {
return null;
}
return a + b;
}
Alles funktioniert, aber wenn es
a
ist
null
oder
undefined
, dann gibt es keinen Punkt die Funktion im Aufruf
maybeGetNumberB
. Wir wissen, dass es trotzdem zurückgegeben wird
null
.
Schreiben wir die Funktion neu:
function maybeAddNumbers() {
const a = maybeGetNumberA();
if (a === null || a === undefined) {
return null;
}
const b = maybeGetNumberB();
if (b === null || b === undefined) {
return null;
}
return a + b;
}
Damit. Anstelle von drei einfachen Codezeilen haben wir sie schnell auf 10 Zeilen aufgebläht (ohne leere). Und jetzt werden Funktionen angewendet
if
, die Sie durchlaufen müssen, um zu verstehen, was die Funktion tut. Und dies ist nur ein lehrreiches Beispiel! Stellen Sie sich eine echte Codebasis mit viel komplexerer Logik vor, die solche Überprüfungen noch schwieriger macht. Ich möchte hier Generatoren verwenden und den Code vereinfachen.
Schau mal:
function* maybeAddNumbers() {
const a = yield maybeGetNumberA();
const b = yield maybeGetNumberB();
return a + b;
}
Was wäre, wenn wir den Ausdruck
yield <smething>
testen lassen könnten, ob es sich um einen
<smething>
echten Wert handelt, und nicht
null
oder
undefined
? Wenn sich herausstellt, dass es sich nicht um eine Zahl handelt, halten wir einfach an und kehren zurück
null
, wie in der vorherigen Version des Codes.
Das heißt, Sie können Code schreiben, der so aussieht, als würde er nur mit realen, definierten Werten funktionieren. Der Generator kann dies überprüfen und entsprechende Maßnahmen für Sie ergreifen! Magie, richtig? Und es ist nicht nur möglich, es ist einfach zu schreiben!
Natürlich haben die Generatoren selbst diese Funktionalität nicht. Sie geben nur Iteratoren zurück, und Sie können Werte wieder in die Generatoren einfügen, wenn Sie möchten. Also müssen wir einen Wrapper schreiben, also sei es
runMaybe
.
Anstatt die Funktion direkt aufzurufen:
const result = maybeAddNumbers();
Wir werden es als Wrapper-Argument bezeichnen:
const result = runMaybe(maybeAddNumbers());
Dieses Muster ist bei Generatoren sehr häufig. An sich wissen sie nicht viel, aber mit Hilfe von selbstgeschriebenen Wrappern können Sie den Generatoren das gewünschte Verhalten geben! Das brauchen wir jetzt.
runMaybe
- eine Funktion, die ein Argument akzeptiert: einen vom Generator erstellten Iterator:
function runMaybe(iterator) {
}
Lassen Sie uns diesen Iterator in einer Schleife ausführen
while
. Dazu müssen Sie den Iterator zum ersten Mal aufrufen und seine Eigenschaft überprüfen
done
:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
}
}
Innerhalb der Schleife haben wir zwei Möglichkeiten. Wenn
result.value
is
null
oder
undefined
, sollte die Iteration sofort gestoppt und zurückgegeben werden
null
. Lass uns das machen:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
if (result.value === null || result.value === undefined) {
return null;
}
}
}
Hier
return
stoppen wir sofort die Iteration mit Hilfe und kehren vom Wrapper zurück
null
. Wenn es sich jedoch
result.value
um eine Zahl handelt, müssen Sie zum Generator "zurückkehren". Wenn die
yield maybeGetNumberA()
Funktion
maybeGetNumberA()
beispielsweise eine Zahl ist, müssen Sie den
yield maybeGetNumberA()
Wert dieser Zahl ersetzen . Lassen Sie mich erklären: Lassen Sie uns sagen , dass das Ergebnis der Berechnung
maybeGetNumberA()
ist 5, dann ersetzen wir
const a = yield maybeGetNumberA();
mit
const a = 5;
. Wie Sie sehen können, wir brauchen nicht zu dem extrahierten Wert zu ändern, ist es genug , um es zu passieren zurück zum Generator.
Wir erinnern uns, dass Sie durch
yield <smething>
einen Wert ersetzen können, indem Sie ihn als Argument an die Methode übergeben
next
in einem Iterator:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
if (result.value === null || result.value === undefined) {
return null;
}
// we are passing result.value back
// to the generator
result = iterator.next(result.value)
}
}
Wie Sie sehen, wird das neue Ergebnis nun wieder in einer Variablen gespeichert
result
. Dies ist möglich, weil wir ausdrücklich die
result
Verwendung von deklariert haben
let
.
Wenn der Generator beim Abrufen eines Werts auf ein
null
/ stößt
undefined
, kehren wir einfach
null
vom Wrapper zurück
runMaybe
.
Es bleibt noch etwas hinzuzufügen, damit der Iterationsprozess endet, ohne
null
/ zu erkennen
undefined
. Wenn wir zwei Zahlen erhalten, müssen wir schließlich ihre Summe aus dem Wrapper zurückgeben!
Der Generator
maybeAddNumbers
endet mit einem Ausdruck
return
. Wir verstehen, dass die Anwesenheit
return <smething>
im Generator bewirkt, dass
next
ein Objekt vom Aufruf zurückgegeben wird
{ value: <smething>, done: true }
. In diesem Fall
while
stoppt die Schleife, da die Eigenschaft
done
einen Wert erhält
true
. Der zuletzt zurückgegebene Wert (in unserem speziellen Fall dieser
a + b
) wird jedoch weiterhin in der Eigenschaft gespeichert
result.value
! Und wir können es einfach zurückgeben:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
if (result.value === null || result.value === undefined) {
return null;
}
result = iterator.next(result.value)
}
// just return the last value
// after the iterator is done
return result.value;
}
Und das ist alles!
Lassen Sie uns Funktionen
maybeGetNumberA
und erstellen und
maybeGetNumberB
sie zuerst reelle Zahlen zurückgeben:
const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => 10;
Lassen Sie uns den Code ausführen und das Ergebnis protokollieren:
function* maybeAddNumbers() {
const a = yield maybeGetNumberA();
const b = yield maybeGetNumberB();
return a + b;
}
const result = runMaybe(maybeAddNumbers());
console.log(result);
Wie erwartet wird die Nummer 15 in der Konsole
angezeigt. Ersetzen Sie nun einen der Begriffe durch
null
:
const maybeGetNumberA = () => null;
const maybeGetNumberB = () => 10;
Bei der Ausführung des Codes erhalten wir
null
!
Es ist jedoch wichtig, dass wir sicherstellen, dass die Funktion
maybeGetNumberB
nicht aufgerufen wird, wenn sie /
maybeGetNumberA
zurückgibt . Lassen Sie uns noch einmal überprüfen, ob die Berechnung erfolgreich war. Fügen Sie dazu einfach die zweite Funktion hinzu :
null
undefined
console.log
const maybeGetNumberA = () => null;
const maybeGetNumberB = () => {
console.log('B');
return 10;
}
Wenn wir den Wrapper korrekt geschrieben haben
runMaybe
, wird der Buchstabe bei der Ausführung dieses Codes
B
nicht in der Konsole angezeigt.
In der Tat, wenn wir den Code ausführen, werden wir einfach sehen
null
. Dies bedeutet, dass der Wrapper den Generator tatsächlich stoppt, sobald er
null
/ erkennt
undefined
.
Der Code funktioniert wie vorgesehen: Er erzeugt eine
null
beliebige Kombination:
const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => 10;
const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => null;
const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => null;
Usw.
Der Vorteil dieses Beispiels liegt jedoch nicht in der Ausführung dieses bestimmten Codes. Es liegt in der Tatsache , dass wir eine erstellt haben universal Wrapper, der arbeiten kann jeden Generator, den Wert extrahieren
null
/
undefined
.
Schreiben wir eine komplexere Funktion:
function* maybeAddFiveNumbers() {
const a = yield maybeGetNumberA();
const b = yield maybeGetNumberB();
const c = yield maybeGetNumberC();
const d = yield maybeGetNumberD();
const e = yield maybeGetNumberE();
return a + b + c + d + e;
}
Sie können dies problemlos in unserem Wrapper tun
runMaybe
! Tatsächlich ist es für den Wrapper nicht einmal wichtig, dass unsere Funktionen Zahlen zurückgeben. Immerhin haben wir den numerischen Typ darin nicht erwähnt. Sie können also jeden Wert im Generator verwenden - Zahlen, Zeichenfolgen, Objekte, Arrays, komplexere Datenstrukturen - und es funktioniert mit unserem Wrapper!
Das inspiriert Entwickler. Mit Generatoren können Sie Ihrem Code benutzerdefinierte Funktionen hinzufügen, die sehr häufig vorkommen (abgesehen von Aufrufen natürlich
yield
). Sie müssen nur einen Wrapper erstellen, der den Generator auf besondere Weise iteriert. Somit fügt der Wrapper dem Generator die notwendige Funktionalität hinzu, die alles sein kann! Generatoren haben nahezu unbegrenzte Möglichkeiten, es geht nur um unsere Vorstellungskraft.