Dieser Artikel wurde von der Huawei Cloud Community geteilt: „ März-Lesewoche·JavaScript, das Sie nicht kennen | ES6-Generator, ein scheinbar synchroner asynchroner Prozesssteuerungsausdrucksstil “, Autor: Ye Yiyi.
Baumeister
Vollständigen Lauf unterbrechen
Es gibt eine Annahme, auf die sich JavaScript-Entwickler in ihrem Code fast immer verlassen: Sobald eine Funktion ausgeführt wird, wird sie bis zum Ende ausgeführt, und kein anderer Code kann sie unterbrechen und dazwischen einfügen.
ES6 führt einen neuen Funktionstyp ein, der dieser Run-to-End-Funktion nicht entspricht. Diese neue Art von Funktion wird als Generator bezeichnet.
var x = 1; Funktion foo() { x++; bar(); // <-- Diese Zeile wird zwischen den Anweisungen x++ und console.log(x) ausgeführt console.log('x:', x); } Funktion bar() { x++; } foo(); // x: 3
Was passiert, wenn bar() nicht vorhanden ist? Offensichtlich wird das Ergebnis 2 und nicht 3 sein. Das Endergebnis ist 3, daher wird bar() zwischen x++ und console.log(x) ausgeführt.
Aber JavaScript ist weder präemptiv noch multithreaded (noch). Es wäre jedoch immer noch möglich, solche Unterbrechungen kooperativ (Parallelität) zu implementieren, wenn foo() selbst irgendwie eine Pause an dieser Stelle im Code anzeigen könnte.
Hier ist der ES6-Code zur Implementierung kooperativer Parallelität:
var x = 1; Funktion* foo() { x++; Ausbeute; // Pause! console.log('x:', x); } Funktion bar() { x++; } //Konstruieren Sie einen Iterator, um diesen Generator zu steuern var it = foo(); //Foo() hier starten! it.next(); console.log('x:', x); // 2 Bar(); console.log('x:', x); // 3 it.next(); // x: 3
- Die Operation it = foo() führt nicht den Generator *foo() aus, sondern erstellt nur einen Iterator (Iterator), der seine Ausführung steuert.
- *foo() pausiert bei der yield-Anweisung, woraufhin der erste it.next()-Aufruf endet. Zu diesem Zeitpunkt läuft *foo() noch und ist aktiv, befindet sich jedoch im angehaltenen Zustand.
- Der letzte Aufruf von it.next() setzt die Ausführung des Generators *foo() an der Stelle fort, an der er angehalten wurde, und führt die Anweisung console.log(..) aus, die den aktuellen Wert von x, 3 verwendet.
Ein Generator ist eine spezielle Art von Funktion, die einmal oder mehrmals gestartet und gestoppt werden kann, aber nicht unbedingt abgeschlossen werden muss.
Eingabe und Ausgabe
Eine Generatorfunktion ist eine spezielle Funktion, die noch einige grundlegende Eigenschaften von Funktionen aufweist. Es kann beispielsweise weiterhin Parameter (d. h. Eingabe) und Rückgabewerte (d. h. Ausgabe) akzeptieren.
Funktion* foo(x, y) { return x * y; } var it = foo(6, 7); var res = it.next(); res.wert; // 42
Übergeben Sie die tatsächlichen Parameter 6 und 7 als Parameter x bzw. y an *foo(..). *foo(..) gibt 42 an den aufrufenden Code zurück.
mehrere Iteratoren
Jedes Mal, wenn Sie einen Iterator erstellen, erstellen Sie tatsächlich implizit eine Instanz des Generators. Es ist die Generatorinstanz, die durch diesen Iterator gesteuert wird.
Mehrere Instanzen desselben Generators können gleichzeitig ausgeführt werden und sogar miteinander interagieren:
Funktion* foo() { var x = Ertrag 2; z++; var y = Ertrag x * z; console.log(x, y, z); } var z = 1; var it1 = foo(); var it2 = foo(); var val1 = it1.next().value; // 2 <-- ergibt 2 var val2 = it2.next().value; // 2 <-- ergibt 2 val1 = it1.next(val2 * 10).value; // 40 <-- x:20, z:2 val2 = it2.next(val1 * 5).value; // 600 <-- x:200, z:3 it1.next(val2 / 2); // y:300 // 20300 3 it2.next(val1 / 4); // y:10 // 200 10 3
Fassen Sie den Ausführungsprozess kurz zusammen:
(1) Zwei Instanzen von *foo() werden gleichzeitig gestartet und die beiden next() erhalten jeweils den Wert 2 aus der yield 2-Anweisung.
(2) val2 * 10, also 2 * 10, wird an die erste Generatorinstanz it1 gesendet, sodass x den Wert 20 erhält. z steigt von 1 auf 2, dann wird 20 * 2 über yield ausgegeben, wobei val1 auf 40 gesetzt wird.
(3) val1 * 5, also 40 * 5, wird an die zweite Generatorinstanz it2 gesendet, sodass x den Wert 200 erhält. z wird erneut von 2 auf 3 erhöht, dann wird 200 * 3 über yield ausgegeben und val2 auf 600 gesetzt.
(4) val2/2, also 600/2, wird an die erste Generatorinstanz it1 gesendet, sodass y den Wert 300 erhält und die Werte von xyz dann jeweils als 20300 3 ausgedruckt werden.
(5) val1/4, also 40/4, wird an die zweite Generatorinstanz it2 gesendet, sodass y den Wert 10 erhält und dann die Werte von xyz jeweils als 200 10 3 ausgedruckt werden.
Generator erzeugt Wert
Produzenten und Iteratoren
Angenommen, Sie möchten eine Folge von Werten generieren, von denen jeder eine spezifische Beziehung zum vorherigen hat. Um dies zu erreichen, ist ein zustandsbehafteter Produzent erforderlich, der sich an den zuletzt erzeugten Wert erinnern kann.
Ein Iterator ist eine genau definierte Schnittstelle, um Schritt für Schritt eine Folge von Werten von einem Produzenten abzurufen. Die Schnittstelle des JavaScript-Iterators besteht darin, next() jedes Mal aufzurufen, wenn Sie den nächsten Wert vom Produzenten erhalten möchten.
Die Standard-Iterator-Schnittstelle kann für numerische Sequenzgeneratoren implementiert werden:
var etwas = (Funktion () { var nextValid; zurückkehren { // for..of-Schleife erforderlich [Symbol.iterator]: function () { gib dies zurück; }, // Standard-Iterator-Schnittstellenmethode Weiter: Funktion () { if (nextVal === undefiniert) { nextVal = 1; } anders { nextVal = 3 * nextVal + 6; } return { done: false, value: nextVal }; }, }; })(); Something.next().value; // 1 Something.next().value; // 9 Something.next().value; // 33 Something.next().value; // 105
Der Aufruf next() gibt ein Objekt zurück. Dieses Objekt verfügt über zwei Eigenschaften: done ist ein boolescher Wert, der den Abschlussstatus des Iterators angibt; value stellt den Iterationswert dar.
wiederholbar
iterable ist ein Objekt, das einen Iterator enthält, der über seine Werte iteriert werden kann.
Ab ES6 besteht die Möglichkeit, einen Iterator aus einem Iterable zu extrahieren, darin, dass das Iterable eine Funktion unterstützen muss, deren Name der spezielle ES6-Symbolwert Symbol.iterator ist. Wenn diese Funktion aufgerufen wird, gibt sie einen Iterator zurück. Normalerweise gibt jeder Aufruf einen neuen Iterator zurück, obwohl dies nicht erforderlich ist.
var a = [1, 3, 5, 7, 9]; für (var v von a) { console.log(v); } // 1 3 5 7 9
a im obigen Codeausschnitt ist iterierbar. Die for..of-Schleife ruft automatisch ihre Symbol.iterator-Funktion auf, um einen Iterator zu erstellen.
for (var v von etwas) { .. }
Die for..of-Schleife erwartet, dass etwas iterierbar ist, also sucht sie nach ihrer Symbol.iterator-Funktion und ruft sie auf.
Generator-Iterator
Der Generator kann als Produzent von Werten betrachtet werden. Wir extrahieren jeweils einen Wert durch den next()-Aufruf der Iterator-Schnittstelle.
Generatoren selbst sind nicht iterierbar. Wenn Sie einen Generator ausführen, erhalten Sie einen Iterator:
Funktion *foo(){ .. } var it = foo();
Der vorherige Produzent einer unendlichen Zahlenfolge kann durch einen Generator implementiert werden, ähnlich wie dieser:
Funktion* etwas() { var nextValid; while (wahr) { if (nextVal === undefiniert) { nextVal = 1; } anders { nextVal = 3 * nextVal + 6; } yield nextVal; } }
Da der Generator bei jedem Yield anhält, bleibt der Status (Umfang) der Funktion *something() erhalten, was bedeutet, dass kein Abschluss erforderlich ist, um den variablen Status zwischen Aufrufen aufrechtzuerhalten.
Asynchroner Iteratorgenerator
Funktion foo(x, y) { ajax('http://some.url.1/? x=' + x + '&y=' + y, function (err, data) { if (irre) { // Einen Fehler an *main() werfen it.throw(err); } anders { //*main() mit empfangenen Daten wiederherstellen it.next(data); } }); } Funktion* main() { versuchen { var text = yield foo(11, 31); console.log(text); } fangen (irrt) { console.error(err); } } var it = main(); //Fang hier an! it.next();
In yield foo(11,31) wird zuerst foo(11,31) aufgerufen, das keinen Wert zurückgibt (d. h. undefiniert zurückgibt), also wird ein Aufruf durchgeführt, um die Daten anzufordern, aber was danach tatsächlich gemacht wird, ist yield undefiniert.
Yield wird hier nicht im Sinne einer Nachrichtenübermittlung verwendet, sondern nur zur Flusskontrolle, um Pausieren/Blockieren zu implementieren. Tatsächlich wird es immer noch eine Nachrichtenübermittlung geben, aber es wird nur eine unidirektionale Nachrichtenübermittlung sein, nachdem der Generator den Betrieb wieder aufgenommen hat.
Schauen Sie sich foo(..) an. Wenn diese Ajax-Anfrage erfolgreich ist, rufen wir auf:
it.next(data);
Dadurch wird der Generator mit den Antwortdaten fortgesetzt, was bedeutet, dass der pausierte Ertragsausdruck diesen Wert direkt erhalten hat. Dieser Wert wird dann der lokalen Variablen text zugewiesen, während der Generatorcode weiter ausgeführt wird.
Zusammenfassen
Fassen wir den Hauptinhalt dieses Artikels zusammen:
- Generator ist ein neuer Funktionstyp in ES6. Er läuft nicht immer bis zum Ende wie gewöhnliche Funktionen. Stattdessen kann ein Generator während des Betriebs angehalten werden (wobei sein Zustand vollständig erhalten bleibt) und zu einem späteren Zeitpunkt an der Stelle fortgesetzt werden, an der er angehalten wurde.
- Das Paar yield/next(..) ist nicht nur ein Kontrollmechanismus, sondern auch ein bidirektionaler Nachrichtenübermittlungsmechanismus. Ertrag .. Der Ausdruck pausiert im Wesentlichen und wartet auf einen Wert, und der nachfolgende next(..)-Aufruf gibt einen Wert (oder implizit undefiniert) an den pausierten Yield-Ausdruck zurück.
- Der Hauptvorteil von Generatoren beim asynchronen Kontrollfluss besteht darin, dass der Code im Generator eine Folge von Schritten ist, die eine Aufgabe auf natürlich synchrone/sequenzielle Weise ausdrückt. Der Trick besteht darin, die mögliche Asynchronität hinter dem Schlüsselwort yield zu verbergen und die Asynchronität in den Teil des Codes zu verschieben, der den Iterator des Generators steuert.
- Generatoren behalten ein sequentielles, synchrones, blockierendes Codemuster für asynchronen Code bei, wodurch das Gehirn dem Code natürlicher folgen kann und einer der beiden Hauptfehler der Callback-basierten Asynchronität behoben wird.
Das erste große Versionsupdate von JetBrains 2024 (2024.1) ist Open Source. Sogar Microsoft plant, dafür zu bezahlen. Warum steht es immer noch in der Kritik? [Wiederhergestellt] Tencent Cloud-Backend stürzte ab: Viele Servicefehler und keine Daten nach der Anmeldung an der Konsole. Deutschland muss auch 30.000 PCs von Windows auf Linux Deepin-IDE migriert haben Bootstrapping! Visual Studio Code 1.88 wurde veröffentlicht. Tencent hat Switch wirklich in eine „denkende Lernmaschine“ verwandelt. Der auf SQLite basierende Web-Client WCDB hat ein umfangreiches Upgrade erhalten.