Detaillierte Erklärung asynchroner Generatoren und asynchroner Iterationen in Node.js

Detaillierte Erklärung asynchroner Generatoren und asynchroner Iterationen in Node.js

Vorwort

Generatorfunktionen gibt es in JavaScript schon seit der Einführung von async/await. Daher muss man beim Erstellen asynchroner Generatoren (Generatoren, die immer ein Promise zurückgeben und auf die gewartet werden kann) viele Dinge beachten.

Heute schauen wir uns asynchrone Generatoren und ihren engen Verwandten, die asynchrone Iteration, an.

Hinweis: Obwohl diese Konzepte für alle modernen JavaScript-Implementierungen gelten sollten, wurde der gesamte Code in diesem Artikel mit den Node.js-Versionen 10, 12 und 14 entwickelt und getestet.

Asynchrongeneratorfunktionen

Schauen Sie sich dieses kleine Programm an:

// Datei: main.js
const createGenerator = Funktion*(){
 Ertrag 'a'
 Ertrag 'b'
 Ertrag 'c'
}

const main = () => {
 const generator = erstelleGenerator()
 für (const item of generator) {
 console.log(Element)
 }
}
hauptsächlich()

Dieser Code definiert eine Generatorfunktion, erstellt ein Generatorobjekt mithilfe der Funktion und iteriert dann mithilfe einer for ... of-Schleife über das Generatorobjekt. Ziemlich normale Sachen – obwohl Sie im wirklichen Leben nie einen Generator für so etwas Triviales verwenden würden. Wenn Sie mit Generatoren und for ... of-Schleifen nicht vertraut sind, lesen Sie bitte die Artikel Javascript-Generatoren und ES6-Schleifen und Iterables. Bevor Sie asynchrone Generatoren verwenden, müssen Sie über solide Kenntnisse zu Generatoren und for...of-Schleifen verfügen.

Angenommen, wir möchten in unserer Generatorfunktion „await“ verwenden. Node.js unterstützt diese Funktion, solange wir die Funktion mit dem Schlüsselwort „async“ deklarieren. Wenn Sie mit asynchronen Funktionen nicht vertraut sind, sehen Sie sich den Artikel „Schreiben asynchroner Aufgaben in modernem JavaScript“ an.

Lassen Sie uns das Programm ändern und „await“ im Generator verwenden.

// Datei: main.js
const createGenerator = asynchrone Funktion*(){
 yield warte auf neues Promise((r) => r('a'))
 Ertrag 'b'
 Ertrag 'c'
}

const main = () => {
 const generator = erstelleGenerator()
 für (const item of generator) {
 console.log(Element)
 }
}
hauptsächlich()

Im wirklichen Leben würden Sie dies auch nicht tun – Sie würden wahrscheinlich auf eine Funktion einer API oder Bibliothek eines Drittanbieters warten. Um das Verständnis für jeden zu erleichtern, sind unsere Beispiele möglichst einfach gehalten.

Wenn Sie versuchen, das obige Programm auszuführen, tritt das folgende Problem auf:

$ Knoten main.js
/Benutzer/alanstorm/Desktop/main.js:9
 für (const item of generator) {
 ^
TypeError: Generator ist nicht iterierbar

JavaScript teilt uns mit, dass dieser Generator „nicht iterierbar“ ist. Auf den ersten Blick könnte es so aussehen, als ob die asynchrone Funktion eines Generators auch bedeutet, dass der von ihm erzeugte Generator nicht iterierbar ist. Dies ist etwas verwirrend, da der Zweck eines Generators darin besteht, Objekte zu erzeugen, die „programmgesteuert“ iterierbar sind.

Finden Sie als Nächstes heraus, was passiert ist.

Überprüfen Sie den Generator

Wenn man sich die iterierbaren Objekte von JavaScript-Generatoren ansieht [1]. Ein Objekt implementiert das Iteratorprotokoll, wenn es über eine nächste Methode verfügt und die nächste Methode ein Objekt mit einer Werteigenschaft, einer Fertigeigenschaft oder sowohl einer Werteigenschaft als auch einer Fertigeigenschaft zurückgibt.

Vergleichen wir die von einer asynchronen Generatorfunktion zurückgegebenen Generatorobjekte mit denen, die von einer regulären Generatorfunktion zurückgegeben werden, indem wir den folgenden Code verwenden:

// Datei: test-program.js
const createGenerator = Funktion*(){
 Ergebnis 'a'
 Ertrag 'b'
 Ertrag 'c'
}

const createAsyncGenerator = asynchrone Funktion*(){
 yield warte auf neues Promise((r) => r('a'))
 Ertrag 'b'
 Ertrag 'c'
}

const main = () => {
 const generator = erstelleGenerator()
 const asyncGenerator = erstelleAsyncGenerator()

 Konsole.log('Generator:',Generator[Symbol.Iterator])
 Konsole.log('asyncGenerator',asyncGenerator[Symbol.iterator])
}
hauptsächlich()

Sie werden sehen, dass Ersteres keine Symbol.iterator-Methode hat, Letzteres jedoch schon.

$ Knoten Testprogramm.js
Generator: [Funktion: [Symbol.Iterator]]
asyncGenerator undefiniert

Beide Generatorobjekte haben eine Next-Methode. Wenn Sie den Testcode ändern, um diese nächste Methode aufzurufen:

// Datei: test-program.js

/* … */

const main = () => {
 const generator = erstelleGenerator()
 const asyncGenerator = erstelleAsyncGenerator()

 Konsole.log('Generator:',Generator.Weiter())
 Konsole.log('asyncGenerator',asyncGenerator.next())
}
hauptsächlich()

Sie werden auf ein weiteres Problem stoßen:

$ Knoten Testprogramm.js
Generator: {Wert: 'a', fertig: false}
asyncGenerator-Versprechen { <ausstehend> }

Damit ein Objekt iterierbar ist, muss die nächste Methode ein Objekt mit den Eigenschaften „Wert“ und „Fertig“ zurückgeben. Eine asynchrone Funktion gibt immer ein Promise-Objekt zurück. Diese Funktion gilt für Generatoren, die mit asynchronen Funktionen erstellt wurden – diese asynchronen Generatoren ergeben immer ein Promise-Objekt.

Dieses Verhalten macht es asynchronen Generatoren unmöglich, das JavaScript-Iterationsprotokoll zu implementieren.

Asynchrone Iteration

Glücklicherweise gibt es eine Möglichkeit, diesen Widerspruch aufzulösen. Wenn Sie sich den Konstruktor oder die Klasse ansehen, die von einem asynchronen Generator zurückgegeben wird

// Datei: test-program.js
/* … */
const main = () => {
 const generator = erstelleGenerator()
 const asyncGenerator = erstelleAsyncGenerator()

 Konsole.log('asyncGenerator',asyncGenerator)
}

Sie können sehen, dass es sich um ein Objekt handelt, dessen Typ oder Klasse oder Konstruktor AsyncGenerator statt Generator ist:

asyncGenerator-Objekt [AsyncGenerator] {}

Obwohl dieses Objekt möglicherweise nicht iterierbar ist, ist es asynchron iterierbar.

Damit ein Objekt asynchron iterierbar ist, muss es eine Symbol.asyncIterator-Methode implementieren. Diese Methode muss ein Objekt zurückgeben, das die asynchrone Version des Iteratorprotokolls implementiert. Das heißt, das Objekt muss über eine Next-Methode verfügen, die ein Promise zurückgibt, und dieses Promise muss letztendlich in ein Objekt mit den Eigenschaften „Done“ und „Value“ aufgelöst werden.

Ein AsyncGenerator-Objekt erfüllt alle diese Bedingungen.

Bleibt die Frage: Wie können wir über ein Objekt iterieren, das nicht iterierbar ist, aber asynchron iteriert werden kann?

für warten … der Schleife

Ein asynchrones Iterable kann manuell iteriert werden, indem nur die nächste Methode des Generators verwendet wird. (Beachten Sie, dass die Hauptfunktion hier jetzt async main ist – dies ermöglicht uns, await innerhalb der Funktion zu verwenden)

// Datei: main.js
const createAsyncGenerator = asynchrone Funktion*(){
 yield warte auf neues Promise((r) => r('a'))
 Ertrag 'b'
 Ertrag 'c'
}

const main = async () => {
 const asyncGenerator = erstelleAsyncGenerator()

 let Ergebnis = { fertig:false }
 während(!Ergebnis.fertig) {
 Ergebnis = warte auf asyncGenerator.next()
 wenn(Ergebnis.fertig) { weiter; }
 console.log(Ergebnis.Wert)
 }
}
hauptsächlich()

Dies ist jedoch nicht der einfachste Looping-Mechanismus. Mir gefällt die While-Schleifenbedingung nicht und ich möchte result.done auch nicht manuell überprüfen. Darüber hinaus muss die Variable result.done im Gültigkeitsbereich sowohl des inneren als auch des äußeren Blocks vorhanden sein.

Glücklicherweise unterstützen die meisten (vielleicht alle?) JavaScript-Implementierungen, die asynchrone Iteratoren unterstützen, auch die spezielle for await ...-Schleifensyntax. Zum Beispiel:

const createAsyncGenerator = asynchrone Funktion*(){
 yield warte auf neues Promise((r) => r('a'))
 Ertrag 'b'
 Ertrag 'c'
}

const main = async () => {
 const asyncGenerator = erstelleAsyncGenerator()
 für warte(const item of asyncGenerator) {
 console.log(Element)
 }
}
hauptsächlich()

Wenn Sie den obigen Code ausführen, werden Sie sehen, dass der asynchrone Generator und das iterierbare Objekt erfolgreich durchlaufen und der vollständig aufgelöste Wert des Promise im Schleifenkörper abgerufen wird.

$ Knoten main.js
A
B
C

Die for await ... of-Schleife bevorzugt Objekte, die das asynchrone Iteratorprotokoll implementieren. Sie können es jedoch verwenden, um über jede Art von iterierbarem Objekt zu iterieren.

für warte(const item of [1,2,3]) {
 console.log(Element)
}

Wenn Sie „for await“ verwenden, sucht Node.js zuerst nach der Methode Symbol.asyncIterator im Objekt. Wenn keines gefunden werden kann, wird auf die Methode Symbol.iterator zurückgegriffen.

Nichtlineare Codeausführung

Wie await führt die for await-Schleife eine nichtlineare Codeausführung in Ihr Programm ein. Das heißt, Ihr Code wird in einer anderen Reihenfolge ausgeführt, als er geschrieben wurde.

Wenn Ihr Programm zum ersten Mal auf eine For-Await-Schleife stößt, ruft es „Next“ für Ihr Objekt auf.

Das Objekt gibt ein Versprechen ab, dann verlässt die Ausführung des Codes Ihre asynchrone Funktion und Ihre Programmausführung wird außerhalb dieser Funktion fortgesetzt.

Sobald Ihr Versprechen eingelöst ist, kehrt die Codeausführung mit diesem Wert zum Schleifenkörper zurück.

Wenn die Schleife endet und es Zeit ist, mit der nächsten Reise fortzufahren, ruft Node.js „next“ für das Objekt auf. Dieser Aufruf erzeugt ein weiteres Versprechen und die Codeausführung verlässt Ihre Funktion erneut. Dieses Muster wiederholt sich, bis das Promise in ein Objekt aufgelöst wird, bei dem „done“ wahr ist. Anschließend wird die Ausführung mit dem Code nach der For-Await-Schleife fortgesetzt.

Das folgende Beispiel veranschaulicht diesen Punkt:

lass count = 0
const getCount = () => {
 zählen++
 gibt `${count}` zurück.
}

const createAsyncGenerator = asynchrone Funktion*() {
 console.log(getCount() + 'createAsyncGenerator wird aufgerufen')

 console.log(getCount() + 'im Begriff, ein Ergebnis zu liefern')
 Ertrag, warte auf neues Versprechen((r)=>r('a'))

 console.log(getCount() + 'createAsyncGenerator erneut aufrufen')
 console.log(getCount() + 'im Begriff, b zu ergeben')
 Ertrag 'b'

 console.log(getCount() + 'createAsyncGenerator erneut aufrufen')
 console.log(getCount() + 'im Begriff, c zu ergeben')
 Ertrag 'c'

 console.log(getCount() + 'createAsyncGenerator erneut aufrufen')
 console.log(getCount() + 'createAsyncGenerator beenden')
}

const main = async () => {
 console.log(getCount() + 'Hauptkonto wird aufgerufen')

 const asyncGenerator = erstelleAsyncGenerator()
 console.log(getCount() + 'Starte Warteschleife')
 für warte(const item of asyncGenerator) {
 console.log(getCount() + 'Eintreten in Warteschleife')
 Konsole.log(getCount() + Element)
 console.log(getCount() + 'Beenden für Warteschleife')
 }
 console.log(getCount() + 'fertig mit for-Await-Schleife')
 console.log(getCount() + 'Hauptverzeichnis verlassen')
}

console.log(getCount() + 'vor dem Aufruf von main')
hauptsächlich()
console.log(getCount() + 'nach dem Aufruf von main')

In diesem Code verwenden Sie nummerierte Protokollierungsanweisungen, mit denen Sie seine Ausführung verfolgen können. Zur Übung sollten Sie das Programm selbst ausführen und die Ergebnisse prüfen.

Asynchrone Iteration ist eine leistungsstarke Technik, die bei der Ausführung Ihres Programms zu Verwirrung führen kann, wenn Sie nicht wissen, wie sie funktioniert.

Zusammenfassen

Dies ist das Ende dieses Artikels über asynchrone Generatoren und asynchrone Iterationen in Node.js. Weitere relevante Inhalte zu asynchronen Generatoren und asynchronen Iterationen in Node.js finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

Das könnte Sie auch interessieren:
  • Detaillierte Erläuterung der asynchronen Prozessimplementierung in Single-Thread-JavaScript
  • Analysieren Sie die Eigenschaften des asynchronen IO-Rückrufs mit einem Thread in JS
  • Asynchrone Programmierung in Javascript: Verstehen Sie Promise wirklich?
  • Detaillierte Erläuterung der ersten Verwendung von Promise in der asynchronen JavaScript-Programmierung
  • JS-Prinzip der asynchronen Ausführung und Rückrufdetails
  • So schreiben Sie asynchrone Aufgaben in modernem JavaScript
  • Lernen Sie die asynchrone Programmierung in Node.js in einem Artikel
  • Detaillierte Erläuterung der Wissenspunkte zur asynchronen Programmierung in nodejs
  • Eine kurze Diskussion über die drei Hauptprobleme von JS: Asynchronität und Single-Thread

<<:  8 Gründe, warum Sie die Xfce-Desktopumgebung für Linux verwenden sollten

>>:  Der Unterschied und die Verwendung von distinct und row_number() over() in SQL

Artikel empfehlen

Studiennotizen zur MySQL Master-Slave-Konfiguration

● Ich hatte vor, einige Cloud-Daten zu kaufen, um...

Grundkenntnisse im Website-Design: Neulinge lesen bitte dies

Heutzutage beginnen viele Leute damit, Websites z...

vue.js Router verschachtelte Routen

Vorwort: Manchmal ist der Hauptteil einer Route d...

Protokoll des Kompilierungs- und Installationsprozesses des Nginx-Quellcodes

Die Installation des RPM-Pakets ist relativ einfa...

Beispielcode zur Implementierung eines Laufschriftformats mit CSS3

Hintergrund Folgendes ist passiert: Luzhu erfuhr ...

Navicat: Mehrere Möglichkeiten zum Ändern des MySQL-Datenbankkennworts

Methode 1: Verwenden Sie den Befehl SET PASSWORD ...

Webdesign: Wenn der Titel nicht vollständig angezeigt werden kann

<br />Ich habe mir heute die neu gestaltete ...

Installations-Tutorial zur dekomprimierten Version von MySQL5.7.21 unter Win10

Installieren Sie die entpackte Version von Mysql ...

Informationen zum CSS-Floating und zum Aufheben des Floatings

Definition von Float Setzt das Element aus dem no...

mysql charset=utf8 verstehen Sie wirklich, was es bedeutet

1. Schauen wir uns zunächst eine Anweisung zur Ta...

Docker stellt eine Verbindung zum Host-Mysql-Vorgang her

Heute muss das Unternehmensprojekt Docker konfigu...

Lösung für die Willkommensmeldung im Notfallmodus beim Booten von CentOS7.4

Heute habe ich eine virtuelle Maschine für ein Ex...