Was ist das Yield-Schlüsselwort in JavaScript?


237

Ich habe von einem "Yield" -Schlüsselwort in JavaScript gehört, aber ich habe eine sehr schlechte Dokumentation darüber gefunden. Kann mir jemand erklären (oder eine Website empfehlen, die erklärt), wie sie verwendet wird und wofür sie verwendet wird?



4
Es wird in MDN erklärt , aber ich denke, das funktioniert nur für Firefox, oder? Wie tragbar ist es? Gibt es eine Möglichkeit, dies auf Chrome oder node.js zu erreichen? PD: Entschuldigung, es ist Javascript v1.7 + , das ist also die Eigenschaft, die Sie bei der Suche nach Unterstützung beachten sollten .
Trylks

1
@Trylks: Generatoren sind in Node seit v0.11.2 verfügbar
Janus Troelsen

@ JanusTroelsen jedoch nur hinter einer Flagge. Sie werden nativ in ioJS
Dan Pantry

Antworten:


85

Die MDN-Dokumentation ist ziemlich gut, IMO.

Die Funktion, die das Schlüsselwort yield enthält, ist ein Generator. Wenn Sie es aufrufen, sind seine formalen Parameter an tatsächliche Argumente gebunden, aber sein Körper wird nicht tatsächlich ausgewertet. Stattdessen wird ein Generator-Iterator zurückgegeben. Jeder Aufruf der next () -Methode des Generator-Iterators führt einen weiteren Durchlauf durch den iterativen Algorithmus durch. Der Wert jedes Schritts ist der Wert, der durch das Schlüsselwort yield angegeben wird. Stellen Sie sich Yield als die Generator-Iterator-Version von return vor, die die Grenze zwischen jeder Iteration des Algorithmus angibt. Jedes Mal, wenn Sie next () aufrufen, wird der Generatorcode aus der Anweisung nach dem Ertrag fortgesetzt.


2
@NicolasBarbulesco Es gibt ein sehr offensichtlich platziertes Beispiel, wenn Sie sich durch die MDN-Dokumentation klicken.
Matt Ball

@MattBall - würde eine solche Funktion als Javascript für PI wie folgt ausreichen: function * PI {PI = ((Math.SQRT8;) / 9801;); } - oder ist für diese PI-Berechnung bereits eine Funktion in Javascript implementiert?
dschinn1001

4
Was bringt es, MDN hier zu zitieren? Ich denke, jeder kann das auf MDN lesen. Besuchen Sie davidwalsh.name/promises , um mehr über sie zu erfahren.
Ejaz Karim

20
Wie kam es zu ~ 80 positiven Stimmen, wenn (a) es sich um eine Kopie der "sehr schlechten Dokumentation" handelt, wie der Fragesteller sie nennt, und (b) nichts Hilfreiches sagt? Weitaus bessere Antworten unten.
www-0av-Com

4
Wenn jemand nach einer Erklärung fragt, ist das Kopieren und Einfügen einer Dokumentation völlig sinnlos. Fragen bedeutet, dass Sie bereits in Dokumenten gesucht haben, diese aber nicht verstanden haben.
Diego

205

Späte Antwort, wahrscheinlich weiß yieldjetzt jeder Bescheid , aber es ist eine bessere Dokumentation hinzugekommen.

Anpassung eines Beispiels aus "Javascript's Future: Generators" von James Long für den offiziellen Harmony-Standard:

function * foo(x) {
    while (true) {
        x = x * 2;
        yield x;
    }
}

"Wenn Sie foo aufrufen, erhalten Sie ein Generator-Objekt zurück, das eine nächste Methode hat."

var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16

So yieldist es return: Du bekommst etwas zurück. return xGibt den Wert von zurück x, gibt jedoch yield xeine Funktion zurück, mit der Sie zum nächsten Wert iterieren können. Nützlich, wenn Sie eine möglicherweise speicherintensive Prozedur haben , die Sie möglicherweise während der Iteration unterbrechen möchten.


13
Hilfreich, aber ich denke, es ist function* foo(x){da
Rana Deep

9
@RanaDeep: Die Funktionssyntax wurde erweitert, um ein optionales * Token hinzuzufügen . Ob Sie es brauchen oder nicht, hängt von der Art der Zukunft ab, in die Sie zurückkehren. Das Detail ist lang: GvR erklärt es für die Python-Implementierung , nach der die Javascript-Implementierung modelliert ist. Die Verwendung function *ist immer richtig, in einigen Fällen jedoch etwas mehr Aufwand als functionbei yield.
Bischof

1
@ Ajedi32 Ja, du hast recht. Harmony standardisierte die Korrelation zwischen function *und yieldund fügte den angegebenen Fehler hinzu ("Ein früher Fehler wird ausgelöst, wenn in einer Nicht-Generator-Funktion ein Ertrag oder ein Ertrag * -Ausdruck auftritt"). Für die ursprüngliche Implementierung von Javascript 1.7 in Firefox war das jedoch nicht erforderlich* . Antwort entsprechend aktualisiert. Vielen Dank!
Bischof

3
@MuhammadUmer Js wird endlich zu einer Sprache, die Sie tatsächlich verwenden können. Es heißt Evolution.
Lukas Liesis

1
Beispiel ist nützlich, aber ... was ist eine Funktion *?
Diego

65

Es ist wirklich einfach, so funktioniert es

  • yieldStichwort hilft einfach zu pausieren und wieder eine Funktion in jederzeit asynchron .
  • Zusätzlich hilft es, den Wert einer Generatorfunktion zurückzugeben .

Nehmen Sie diese einfache Generatorfunktion :

function* process() {
    console.log('Start process 1');
    console.log('Pause process2 until call next()');

    yield;

    console.log('Resumed process2');
    console.log('Pause process3 until call next()');

    let parms = yield {age: 12};
    console.log("Passed by final process next(90): " + parms);

    console.log('Resumed process3');
    console.log('End of the process function');
}

let _process = process ();

Bis Sie das nennen _process.next () es würde nicht die Ausführungs ersten 2 Zeilen von Code, dann ist die erste Ausbeute wird pausiert die Funktion. Um wieder auf die Funktion bis zum nächsten Pause Punkt ( Ausbeute Schlüsselwort ) Sie müssen rufen _process.next () .

Sie können sich vorstellen, dass mehrere Ausbeuten die Haltepunkte in einem Javascript-Debugger innerhalb einer einzelnen Funktion sind. Bis Sie anweisen, zum nächsten Haltepunkt zu navigieren, wird der Codeblock nicht ausgeführt. ( Hinweis : ohne die gesamte Anwendung zu blockieren)

Aber während Ausbeute führt diese Pause und Lebenslauf Verhalten kann es einige Ergebnisse zurück als auch {value: any, done: boolean} nach der vorherige Funktion wir keine Werte emittieren haben. Wenn wir die vorherige Ausgabe untersuchen, wird dieselbe { value: undefined, done: false } mit einem undefinierten Wert angezeigt .

Lassen Sie uns in das Yield-Schlüsselwort eintauchen. Optional können Sie einen Ausdruck hinzufügen und einen optionalen Standardwert zuweisen . (Offizielle Dokumentensyntax)

[rv] = yield [expression];

Ausdruck : Wert, der von der Generatorfunktion zurückgegeben werden soll

yield any;
yield {age: 12};

rv : Gibt den optionalen Wert zurück, der an die next () -Methode des Generators übergeben wurde

Mit diesem Mechanismus können Sie einfach Parameter an die Funktion process () übergeben, um verschiedene Fließteile auszuführen.

let val = yield 99; 

_process.next(10);
now the val will be 10 

Versuchen Sie es jetzt

Verwendungen

  • Faule Bewertung
  • Unendliche Sequenzen
  • Asynchrone Steuerungsabläufe

Verweise:


54

Wenn ich die Antwort von Nick Sotiros vereinfache (was ich großartig finde), ist es meiner Meinung nach am besten zu beschreiben, wie man mit dem Codieren beginnen würde yield.

Meiner Meinung nach besteht der größte Vorteil der Verwendung yielddarin, dass alle verschachtelten Rückrufprobleme, die wir im Code sehen, beseitigt werden. Es ist schwer zu sehen, wie es zuerst geht, weshalb ich beschlossen habe, diese Antwort zu schreiben (für mich selbst und hoffentlich für andere!)

Die Art und Weise, wie dies geschieht, besteht darin, die Idee einer Co-Routine einzuführen, die eine Funktion ist, die freiwillig anhalten / pausieren kann, bis sie das bekommt, was sie benötigt. In Javascript wird dies mit bezeichnet function*. Nur function*Funktionen können verwenden yield.

Hier ist ein typisches Javascript:

loadFromDB('query', function (err, result) {
  // Do something with the result or handle the error
})

Dies ist umständlich, da sich jetzt Ihr gesamter Code (der offensichtlich auf diesen loadFromDBAnruf warten muss) in diesem hässlich aussehenden Rückruf befinden muss. Das ist aus ein paar Gründen schlecht ...

  • Ihr gesamter Code wird eine Ebene in eingerückt
  • Sie haben dieses Ziel, })das Sie überall im Auge behalten müssen
  • All dieser zusätzliche function (err, result)Jargon
  • Nicht genau klar, dass Sie dies tun, um einen Wert zuzuweisen result

Auf der anderen Seite kann yieldall dies mit Hilfe des netten Co-Routine-Frameworks in einer Zeile erledigt werden.

function* main() {
  var result = yield loadFromDB('query')
}

Und so wird Ihre Hauptfunktion jetzt bei Bedarf nachgeben, wenn sie auf das Laden von Variablen und Dingen warten muss. Um dies auszuführen, müssen Sie jetzt eine normale (Nicht-Coroutine-Funktion) aufrufen . Ein einfaches Co-Routine-Framework kann dieses Problem beheben, sodass Sie nur Folgendes ausführen müssen:

start(main())

Und der Start ist definiert (aus der Antwort von Nick Sotiro)

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

Und jetzt können Sie schönen Code haben, der viel besser lesbar, leicht zu löschen und ohne Einrückungen, Funktionen usw. herumspielen muss.

Eine interessante Beobachtung ist, dass es sich in diesem Beispiel yieldeigentlich nur um ein Schlüsselwort handelt, das Sie einer Funktion mit einem Rückruf vorlegen können.

function* main() {
  console.log(yield function(cb) { cb(null, "Hello World") })
}

Würde "Hallo Welt" drucken. Sie können also jede Rückruffunktion tatsächlich verwenden, yieldindem Sie einfach dieselbe Funktionssignatur (ohne cb) erstellen und function (cb) {}wie folgt zurückkehren :

function yieldAsyncFunc(arg1, arg2) {
  return function (cb) {
    realAsyncFunc(arg1, arg2, cb)
  }
}

Hoffentlich können Sie mit diesem Wissen saubereren, besser lesbaren Code schreiben, der leicht zu löschen ist !


a function*ist nur eine reguläre Funktion ohne Ausbeute?
Abdul

Ich denke, Sie meinen, das function *ist eine Funktion, die Ertrag enthält . Es ist eine spezielle Funktion, die als Generator bezeichnet wird.
Leander

7
Für Leute, die bereits yieldüberall verwenden, ist dies sicher sinnvoller als die Rückrufe, aber ich sehe nicht, wie dies besser lesbar ist als Rückrufe.
Palswim

Dieser Artikel ist schwer zu verstehen
Martian2049

18

Um eine vollständige Antwort zu geben: yieldfunktioniert ähnlich return, aber in einem Generator.

Für das allgemein gegebene Beispiel funktioniert dies wie folgt:

function *squareGen(x) {
    var i;
    for (i = 0; i < x; i++) {
        yield i*i;
    }
}

var gen = squareGen(3);

console.log(gen.next().value); // prints 0
console.log(gen.next().value); // prints 1
console.log(gen.next().value); // prints 4

Es gibt aber auch einen zweiten Zweck des Yield-Schlüsselworts. Es kann verwendet werden, um Werte an den Generator zu senden.

Zur Verdeutlichung ein kleines Beispiel:

function *sendStuff() {
    y = yield (0);
    yield y*y;
}

var gen = sendStuff();

console.log(gen.next().value); // prints 0
console.log(gen.next(2).value); // prints 4

Dies funktioniert, wenn der Wert 2zugewiesen wird y, indem er an den Generator gesendet wird, nachdem er bei der ersten Ausbeute (die zurückgegeben wurde 0) gestoppt wurde .

Dies ermöglicht es uns, einige wirklich funky Sachen zu machen. (Coroutine nachschlagen)



6

yield kann auch verwendet werden, um die Rückrufhölle mit einem Coroutine-Framework zu eliminieren.

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

// with nodejs as 'node --harmony'
fs = require('fs');
function read(path) {
    return function(callback) { fs.readFile(path, {encoding:'utf8'}, callback); };
}

function* routine() {
    text = yield read('/path/to/some/file.txt');
    console.log(text);
}

// with mdn javascript 1.7
http.get = function(url) {
    return function(callback) { 
        // make xhr request object, 
        // use callback(null, resonseText) on status 200,
        // or callback(responseText) on status 500
    };
};

function* routine() {
    text = yield http.get('/path/to/some/file.txt');
    console.log(text);
}

// invoked as.., on both mdn and nodejs

start(routine());

4

Fibonacci-Sequenzgenerator mit dem Schlüsselwort yield.

function* fibbonaci(){
    var a = -1, b = 1, c;
    while(1){
        c = a + b;
        a = b;
        b = c;
        yield c;
    }   
}

var fibonacciGenerator = fibbonaci();
fibonacciGenerator.next().value; // 0 
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 2 

4

Yeild Schlüsselwort in der JavaScript-Funktion macht es Generator,

Was ist Generator in JavaScript?

Ein Generator ist eine Funktion, die anstelle eines einzelnen Werts eine Folge von Ergebnissen erzeugt, dh Sie erzeugen eine Reihe von Werten

Das heißt, Generatoren helfen uns, asynchron mit den Hilfe-Iteratoren zu arbeiten. Oh, was sind nun die Hack-Iteratoren? Ja wirklich?

Iteratoren sind Mittelwerte, über die wir einzeln auf Elemente zugreifen können

Von wo aus können wir mit dem Iterator einzeln auf das Element zugreifen? Es hilft uns, über Generatorfunktionen auf Elemente zuzugreifen.

Generatorfunktionen sind solche, in denen wir yeildSchlüsselwörter verwenden. Yield-Schlüsselwörter helfen uns, die Ausführung der Funktion anzuhalten und wieder aufzunehmen

Hier ist ein kurzes Beispiel

function *getMeDrink() {

    let question1 = yield 'soda or beer' // execution will pause here because of yield

 if (question1 == 'soda') {

            return 'here you get your soda'

    }

    if (question1 == 'beer') {

        let question2 = yield 'Whats your age' // execution will pause here because of yield

        if (question2 > 18) {

            return "ok you are eligible for it"

        } else {

            return 'Shhhh!!!!'

        }
    }
}


let _getMeDrink = getMeDrink() // initialize it

_getMeDrink.next().value  // "soda or beer"

_getMeDrink.next('beer').value  // "Whats your age"

_getMeDrink.next('20').value  // "ok you are eligible for it"

_getMeDrink.next().value // undefined

Lassen Sie mich kurz erklären, was los ist

Sie haben festgestellt, dass die Ausführung bei jedem yeildSchlüsselwort angehalten wird und wir yieldmithilfe des Iterators zuerst darauf zugreifen können.next()

Dies iteriert yieldnacheinander zu allen Schlüsselwörtern und gibt dann undefiniert zurück, wenn yieldin einfachen Worten keine Schlüsselwörter mehr vorhanden sind. Sie können sagen, dass das yieldSchlüsselwort ein Haltepunkt ist, an dem die Funktion jedes Mal pausiert und nur fortgesetzt wird, wenn sie mit dem Iterator aufgerufen wird

Für unseren Fall: _getMeDrink.next()Dies ist ein Beispiel für einen Iterator, der uns hilft, auf jeden Haltepunkt in der Funktion zuzugreifen

Beispiel für Generatoren: async/await

Wenn Sie die Implementierung von async/await sehen, generator functions & promiseswerden Sie sehen, dass sie verwendet werden, um async/awaitArbeit zu machen

Bitte weisen Sie darauf hin, dass Vorschläge willkommen sind


3

Abhängigkeit zwischen asynchronen Javascript-Aufrufen.

Ein weiteres gutes Beispiel dafür, wie Ausbeute verwendet werden kann.

function request(url) {
  axios.get(url).then((reponse) => {
    it.next(response);
  })
}

function* main() {
  const result1 = yield request('http://some.api.com' );
  const result2 = yield request('http://some.otherapi?id=' + result1.id );
  console.log('Your response is: ' + result2.value);
}

var it = main();
it.next()


0

Bevor Sie etwas über den Ertrag lernen, müssen Sie sich mit Generatoren auskennen. Generatoren werden mit der function*Syntax erstellt. Generatorfunktionen führen keinen Code aus, sondern geben einen Iteratortyp zurück, der als Generator bezeichnet wird. Wenn mit der nextMethode ein Wert angegeben wird, wird die Generatorfunktion so lange ausgeführt, bis sie auf ein Yield-Schlüsselwort stößt. Mit using erhalten yieldSie ein Objekt zurück, das zwei Werte enthält, einer ist value und der andere ist done (boolean). Der Wert kann ein Array, ein Objekt usw. sein.


0

Ein einfaches Beispiel:

const strArr = ["red", "green", "blue", "black"];

const strGen = function*() {
    for(let str of strArr) {
        yield str;
    }
};

let gen = strGen();

for (let i = 0; i < 5; i++) {
    console.log(gen.next())
}

//prints: {value: "red", done: false} -> 5 times with different colors, if you try it again as below:

console.log(gen.next());

//prints: {value: undefined, done: true}

0

Ich versuche auch, das Yield-Schlüsselwort zu verstehen. Nach meinem derzeitigen Verständnis funktioniert das Ertragsschlüsselwort im Generator wie ein CPU-Kontextwechsel. Wenn die Yield-Anweisung ausgeführt wird, werden alle Status (z. B. lokale Variablen) gespeichert.

Außerdem wird ein direktes Ergebnisobjekt an den Aufrufer zurückgegeben, z. B. {value: 0, done: false}. Der Aufrufer kann dieses Ergebnisobjekt verwenden, um zu entscheiden, ob der Generator durch Aufrufen von next () erneut aufgeweckt werden soll (durch Aufrufen von next () wird die Ausführung wiederholt).

Ein weiterer wichtiger Punkt ist, dass ein Wert für eine lokale Variable festgelegt werden kann. Dieser Wert kann vom Aufrufer 'next ()' beim 'Aufwecken' des Generators übergeben werden. Beispiel: it.next ('valueToPass') lautet wie folgt: "resultValue = yield slowQuery (1);" Genau wie beim Aufwecken einer nächsten Ausführung kann der Aufrufer ein laufendes Ergebnis in die Ausführung einfügen (in eine lokale Variable einfügen). Für diese Ausführung gibt es also zwei Arten von Zuständen:

  1. der Kontext, der in der letzten Ausführung gespeichert wurde.

  2. Die vom Trigger dieser Ausführung injizierten Werte.

Mit dieser Funktion kann der Generator mehrere asynchrone Vorgänge sortieren. Das Ergebnis der ersten asynchronen Abfrage wird durch Festlegen der lokalen Variablen (resultValue im obigen Beispiel) an die zweite übergeben. Die zweite asynchrone Abfrage kann nur durch die Antwort der ersten asynchronen Abfrage ausgelöst werden. Dann kann die zweite asynchrone Abfrage den Wert der lokalen Variablen überprüfen, um über die nächsten Schritte zu entscheiden, da die lokale Variable ein injizierter Wert aus der Antwort der ersten Abfrage ist.

Die Schwierigkeiten bei asynchronen Abfragen sind:

  1. Rückruf Hölle

  2. Verlust des Kontexts, sofern sie nicht als Parameter im Rückruf übergeben werden.

Ertrag und Generator können bei beiden helfen.

Ohne Ertrag und Generator erfordert das Aussortieren mehrerer asynchroner Abfragen einen verschachtelten Rückruf mit Parametern als Kontext, der nicht einfach zu lesen und zu warten ist.

Unten finden Sie ein Beispiel für verkettete asynchrone Abfragen, die mit nodejs ausgeführt werden:

const axios = require('axios');

function slowQuery(url) {        
    axios.get(url)
    .then(function (response) {
            it.next(1);
    })
    .catch(function (error) {
            it.next(0);
    })
}

function* myGen(i=0) {
    let queryResult = 0;

    console.log("query1", queryResult);
    queryResult = yield slowQuery('https://google.com');


    if(queryResult == 1) {
        console.log("query2", queryResult);
        //change it to the correct url and run again.
        queryResult = yield slowQuery('https://1111111111google.com');
    }

    if(queryResult == 1) {
        console.log("query3", queryResult);
        queryResult =  yield slowQuery('https://google.com');
    } else {
        console.log("query4", queryResult);
        queryResult = yield slowQuery('https://google.com');
    }
}

console.log("+++++++++++start+++++++++++");
let it = myGen();
let result = it.next();
console.log("+++++++++++end+++++++++++");

Unten ist das laufende Ergebnis:

+++++++++++ Start ++++++++++++

query1 0

+++++++++++ Ende ++++++++++++

query2 1

query4 0

Das folgende Zustandsmuster kann das Gleiche für das obige Beispiel tun:

const axios = require('axios');

function slowQuery(url) {
    axios.get(url)
        .then(function (response) {
            sm.next(1);
        })
        .catch(function (error) {
            sm.next(0);
        })
}

class StateMachine {
        constructor () {
            this.handler = handlerA;
            this.next = (result = 1) => this.handler(this, result);
        }
}

const handlerA = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    console.log("query1", queryResult);
                                    slowQuery('https://google.com');
                                    sm.handler = handlerB; //similar with yield;
                                };

const handlerB = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    if(queryResult == 1) {
                                        console.log("query2", queryResult);
                                        slowQuery('https://1111111111google.com');
                                    }
                                    sm.handler = handlerC; //similar with yield;
                                };

const handlerC = (sm, result) => {
                                    const queryResult = result; //similar with generator injection;
                                    if (result == 1 ) {
                                        console.log("query3", queryResult);
                                        slowQuery('https://google.com');
                                    } else {
                                        console.log("query4", queryResult);
                                        slowQuery('https://google.com');
                                    }
                                    sm.handler = handlerEnd; //similar with yield;
                                };

const handlerEnd = (sm, result) => {};

console.log("+++++++++++start+++++++++++");
const sm = new StateMachine();
sm.next();
console.log("+++++++++++end+++++++++++");

Es folgt das laufende Ergebnis:

+++++++++++ Start ++++++++++++

query1 0

+++++++++++ Ende ++++++++++++

query2 1

query4 0


0

Vergessen Sie nicht die sehr hilfreiche Syntax 'x des Generators', um den Generator zu durchlaufen. Die next () -Funktion muss überhaupt nicht verwendet werden.

function* square(x){
    for(i=0;i<100;i++){
        x = x * 2;
        yield x;        
    }   
}

var gen = square(2);
for(x of gen){
   console.log(x);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.