Node.js Generators & Vergleichen mit Rückrufen

⚡ Intelligente Zusammenfassung

GeneratorIn Node.js sind `yield`-Funktionen, deren Ausführung bei Bedarf unterbrochen und fortgesetzt werden kann. Das Schlüsselwort `yield` pausiert die Funktion, die Methode `next` setzt sie fort, und diese Kombination vereinfacht tief verschachtelte Callback-Funktionen zu lesbarem, sequenziellem Code.

  • ⏸️ Kernverhalten: Der Generator unterbricht die Ausführung bei jedem yield-Befehl und setzt sie genau an der Stelle fort, an der er unterbrochen wurde, wenn next aufgerufen wird.
  • ✳️ Erklärung: Ein Sternchen nach dem Funktionsschlüsselwort kennzeichnet die Funktion als Generatorfunktion und nicht als gewöhnliche Funktion.
  • 🔄 Zwei-Wege-Kanal: Der an next übergebene Wert wird zum Ergebnis des yield-Ausdrucks, der die Funktion angehalten hat.
  • 🪆 Rückruf-Hölle: Verschachtelte Rückruffunktionen werden mit zunehmender Anzahl an Schritten unleserlich, und genau dieses Problem wurden durch die Einführung von Generatoren gelöst.
  • 🏃 Läufer/in benötigt: Ein Generator steuert sich nicht selbst an; eine Runner-Funktion muss die Funktion next wiederholt aufrufen, damit die asynchrone Arbeit fortgesetzt werden kann.
  • 🦥 Schlampige Ausführung: Durch Anhalten und Fortsetzen kann ein Programm Werte nur dann abrufen, wenn sie benötigt werden.

Node.js Generators Im Vergleich zu Rückrufen

In dieser Anleitung betrachten wir Generatoren und wie sie sich von Callbacks unterscheiden.

Was sind Generatoren?

Generators wurde populär in Node.js aufgrund dessen, wozu sie fähig sind.

  • Generators sind Funktionsausführungen, die angehalten und zu einem späteren Zeitpunkt fortgesetzt werden können.
  • Generators sind nützlich für Konzepte wie „verzögerte Ausführung“. Indem die Ausführung angehalten und nach Belieben fortgesetzt wird, werden Werte nur dann abgerufen, wenn sie benötigt werden.

GeneratorEs gibt zwei Hauptmethoden, die im Folgenden beschrieben werden.

  1. Ertragsmethode — wird innerhalb einer Funktion aufgerufen, um die Ausführung genau an der Stelle anzuhalten, an der yield erscheint.
  2. Nächste Methode — wird von der Hauptanwendung aufgerufen, um eine Funktion fortzusetzen, die unterbrochen wurde. Die Ausführung wird bis zum nächsten Aufruf fortgesetzt. yieldoder bis zum Ende der Funktion.

Betrachten wir ein Beispiel für die Verwendung von Generatoren. Das Beispiel definiert eine einfache Additionsfunktion, die zwei Zahlen addiert und die Ausführung an verschiedenen Stellen anhält, um das Verhalten von Generatoren zu veranschaulichen.

Node.js Generators

function* Add(x) {
    yield x + 1;
    // The value passed into the second next() call lands here
    var y = yield(null);
    return x + y;
}

var gen = Add(5);

gen.next();      // runs to the first yield, returns { value: 6, done: false }
gen.next(6);     // resumes, y becomes 6, returns { value: 11, done: true }

Code Erläuterung

  1. Der erste Schritt besteht darin, die Generatorfunktion zu definieren. Beachten Sie die * Um die hinzugefügte function Schlüsselwort. Die Funktion wird aufgerufen. Add und nimmt einen Parameter entgegen x.
  2. Das yield Das Schlüsselwort ist spezifisch für Generatoren und unterbricht die Funktion mitten in der Ausführung. Die Ausführung wird hier angehalten, bis next() wird aufgerufen.
  3. Der zweite yield Die Funktion wird sowohl angehalten als auch empfangen: der Wert, der an die folgende Funktion übergeben wird. next() Der Aufruf wird zum Wert von y.
  4. maximal einfach anrufen gen.next() Beim ersten Durchlauf wird die Funktion bis zum ersten ausgeführt yield. Berufung gen.next(6) setzt es fort, weist 6 zu y, und kehrt zurück x + y.

⚠️ Korrektur: Die ursprüngliche Auflistung enthielt die Zeile y = 6 gleich nach var y = yield(null);Diese Zuweisung überschreibt jeden vorhandenen Wert. next() Lieferungen, was den Zweck des Beispiels zunichtemacht und das zweite yield bedeutungslos. Der Wert wird nun übergeben durch gen.next(6)So empfängt ein Generator tatsächlich Daten vom Aufrufer.

Callbacks vs. Generatoren

GeneratorSie lösen das Problem, das als Callback-Hölle bekannt ist. Callback-Funktionen können während der Entwicklung einer Node.js-Anwendung so tief verschachtelt werden, dass sie schwer nachzuvollziehen sind.

Hier kommen Generatoren ins Spiel. Ein häufiges Beispiel ist die Erstellung von Timerfunktionen. Das folgende Beispiel erzeugt eine einfache Zeitverzögerungsfunktion und ruft diese anschließend mit Verzögerungen von 1000, 2000 und 3000 ms auf.

Schritt 1) Definiere die Callback-Funktion mit dem notwendigen Zeitverzögerungscode.

Rückrufe vs. Generators

function Timedelay(ptime, callback) {

    setTimeout(function() {

        callback("Pausing for " + ptime);

    }, ptime);   // the delay must use ptime, the parameter name
}

Code Erläuterung

  1. Eine Funktion namens Timedelay wird mit einem Parameter erstellt ptime, das die Anzahl der Millisekunden angibt, für die pausiert werden soll.
  2. setTimeout wartet die angegebene Anzahl Millisekunden und ruft dann die bereitgestellte Callback-Funktion auf.

⚠️ Korrektur: Der ursprüngliche Code wurde bestanden time als zweites Argument für setTimeoutaber keine Variable namens time existiert — der Parameter ist ptimeSo wie es geschrieben steht, wirft es ReferenceError: time is not defined.

Schritt 2) Betrachten wir nun den Code mit Callback-Funktionen. Um Verzögerungen von 1000, 2000 und 3000 Millisekunden zu verketten, werden die Callback-Funktionen ineinander verschachtelt.

Rückrufe vs. Generators

Timedelay(1000, function(message) {

    console.log(message);

    Timedelay(2000, function(message) {

        console.log(message);

        Timedelay(3000, function(message) {

            console.log(message);
        });
    });
});

Code Erläuterung

  1. Timedelay wird als Callback mit dem Wert 1000 aufgerufen.
  2. Innerhalb dieses Rückrufs Timedelay wird mit 2000 erneut aufgerufen.
  3. In diesem einen, Timedelay wird ein drittes Mal mit 3000 aufgerufen.

⚠️ Korrektur: jeder Rückruf im ursprünglichen Protokoll msgDer Parameter heißt jedoch messageAlle drei Linien warfen ReferenceError: msg is not defined.

Aus dem obigen Code kann man sehen, dass er mit jedem zusätzlichen Aufruf unübersichtlicher wird – diese Verschachtelung ist das, was die Callback-Hölle beschreibt.

Schritt 3) Betrachten wir nun dasselbe Verhalten, geschrieben mit Generatoren. Die Sequenz wird als drei flache Zeilen anstatt als drei Verschachtelungsebenen gelesen.

Rückrufe vs. Generators

function* Messages() {
    console.log(yield Timedelay(1000));
    console.log(yield Timedelay(2000));
    console.log(yield Timedelay(3000));
}

Code Erläuterung

  1. Es wird eine Generatorfunktion definiert, die die Timedelay Funktion.
  2. yield wird verwendet mit Timedelay und 1000 als Parameterwert.
  3. yield wird erneut bei 2000 und dann noch einmal bei 3000 verwendet.

⚠️ Korrektur: der ursprüngliche Autor schrieb console,log(...) mit einem Komma anstelle eines Punktes in allen drei Zeilen, was einen Syntaxfehler darstellt und verhindert, dass die Datei überhaupt analysiert werden kann.

So führen Sie ein Generator mit einer Läuferfunktion

Der obige Generator liest sich zwar elegant, tut aber von selbst nichts. Genau dieses Detail lassen die meisten Erklärungen aus, und deshalb wirkt die einfache Version wie Zauberei. Ein Generator ist wie eine pausierte Maschine; irgendetwas muss den Knopf immer wieder drücken. Messages() Es wird lediglich ein Generatorobjekt erstellt und sofort zurückgegeben; es startet kein Timer und es wird nichts ausgegeben.

Das fehlende Puzzleteil ist ein Runner: eine kleine Funktion, die aufruft next()wartet, bis die asynchrone Arbeit abgeschlossen ist, und ruft dann auf next() und erneut das Ergebnis.

// Timedelay now hands its result back to the runner
function Timedelay(ptime) {
    return function(resume) {
        setTimeout(function() {
            resume("Pausing for " + ptime);
        }, ptime);
    };
}

// The runner drives the generator to completion
function run(generatorFunction) {
    var it = generatorFunction();

    function step(value) {
        var result = it.next(value);
        if (result.done) return;
        // result.value is the function returned by Timedelay
        result.value(step);
    }

    step();
}

run(Messages);

Drei Punkte sind dafür notwendig. Erstens: Timedelay Es nimmt keine Callback-Funktion mehr direkt entgegen; stattdessen gibt es eine Funktion zurück, die der Runner aufrufen kann und die Kontrolle zurückgibt, sobald der Timer auslöst. Zweitens, it.next(value) speist das vorherige Ergebnis in den pausierten Generator ein, wodurch dies ermöglicht wird yield Auf der rechten Seite einer Aufgabe zu erscheinen. Drittens stoppt der Läufer, sobald result.done Wenn die Bedingung wahr wird, endet die Rekursion auf natürliche Weise. Dieses Runner-Muster wird von Bibliotheken wie co bereitgestellt und ist der Mechanismus, der async/await später in die Sprache selbst aufgenommen.

Generators vs. async/await: Welche Methode ist die richtige?

Generators waren die Standardmethode, um flachen asynchronen Code zu schreiben, bevor async/await Es existierte bereits, und beide basieren auf der gleichen Grundidee, eine Funktion mittendrin anzuhalten. Node unterstützt dies. async/await Seit Version 7.6 ist dies nativ möglich, was die Empfehlung für alle, die heute eine neue Aufgabe beginnen, verändert.

Aspekt Generatoren async / warten
Läufer/in gesucht Ja, von Ihnen oder einer Bibliothek verfasst. Nein, es ist in die Sprache integriert.
Pause-Schlüsselwort yield await
Fehlerbehandlung Der Läufer muss Fehler weiterleiten Gewöhnlicher Versuch und Fang
Kann teilweise pausieren Ja, und kann selektiv wieder aufgenommen werden. Nein, läuft bis zum Ende
am besten für Faule Sequenzen und unendliche Ströme Gewöhnliche asynchrone Arbeit

Arbeiten jederzeit weiterbearbeiten können. Jede Präsentation und jeder KI-Avatar, den Sie von Grund auf neu erstellen oder hochladen, async/await Für asynchronen Ablauf in neuem Code ist dies vorteilhaft, da der Runner entfernt und die üblichen try- und catch-Blöcke wiederhergestellt werden. Generatoren sollten nur für ihre Kernaufgaben verwendet werden: das verzögerte Erzeugen von Werten, das Iterieren über große oder unendliche Sequenzen und alle Fälle, in denen der Nutzer den genauen Zeitpunkt der Berechnung des nächsten Werts bestimmt.

gemeinsam Generator Fehler und wie man sie behebt

Die meisten Generatorprobleme entstehen dadurch, dass man vergisst, dass die Funktion inaktiv ist, bis sie aufgerufen wird. next()oder durch kleine Syntaxfehler um den Stern herum. Jedes der folgenden Symptome nennt seine Ursache und seine Lösung.

  • Es passiert nichts, wenn der Generator aufgerufen wird: Der Aufruf gibt einen Iterator zurück, kein Ergebnis. next()oder übergebe den Ball an einen Läufer.
  • Syntaxfehler bei yield: Der Stern fehlt. yield ist nur innerhalb eines gültig function*.
  • Der Wert von next() ist undefiniert: Der Erste next() Der Aufruf kann keinen Wert liefern, da der Generator noch nicht erreicht hat. yield Noch nicht. Senden Sie Daten ab dem zweiten Anruf.
  • Endlosschleife in einem Läufer: result.done wird nie überprüft, next() Die Funktion wird immer wieder aufgerufen, auch nach dem Ende. Die Bedingung „Immer zurückkehren, wenn fertig“ ist wahr.
  • Fehler verschwinden stillschweigend: Eine Ablehnung innerhalb des asynchronen Schritts wird niemals an den Generator zurückgegeben. Leiten Sie sie weiter mit it.throw(err) damit ein innerhalb des Generators geschriebener try-catch-Block das Problem tatsächlich erkennen und behandeln kann.

Häufig gestellte Fragen

Ein Objekt mit zwei Eigenschaften: value, wobei alles, was abgegeben wurde, festgehalten wurde, und doneEine boolesche Variable, die auf „true“ gesetzt wird, sobald der Generator fertig ist. Die Testläufe dienen dazu, den Stoppzeitpunkt zu bestimmen.

Ja. Ein Generator mit einer Endlosschleife ist sicher, da erst nach dem Aufruf von `next()` Berechnungen durchgeführt werden. Daher eignen sich Generatoren gut für Datenströme von Kennungen oder paginierte Ergebnisse.

Ja. AI Die Tools ersetzen `yield` durch `await` und lassen den Runner komplett weg. RevBeachten Sie Lazy Sequences genau, denn ein Generator, der nie fertig wird, hat kein direktes async/await-Äquivalent.

AI-Assistenten tracEs kann darauf ankommen, ob der Runner den nächsten Aufruf abgebrochen hat, ob ein Resume-Callback nie aufgerufen wurde oder ob das Ergebnis nie erreicht wurde. Diese drei Gründe sind für nahezu jeden blockierten Generator verantwortlich.

Nein. Ein asynchroner Generator wird deklariert. async function* und wird verbraucht mit for await...ofEs vereint beide Ideen und eignet sich für das Streaming asynchroner Daten wie Datenbank-Cursor.

Fassen Sie diesen Beitrag mit folgenden Worten zusammen: