Suche…


Einführung

Generatorfunktionen (durch das Schlüsselwort function* definiert) werden als Coroutinen ausgeführt und erzeugen eine Reihe von Werten, wenn sie durch einen Iterator angefordert werden.

Syntax

  • Funktion * Name (Parameter) {Ertragswert; Rückgabewert }
  • Generator = Name (Argumente)
  • {value, done} = generator.next (wert)
  • {value, done} = generator.return (wert)
  • generator.throw (Fehler)

Bemerkungen

Generatorfunktionen sind eine Funktion, die im Rahmen der ES 2015-Spezifikation eingeführt wurde und nicht in allen Browsern verfügbar ist. Sie werden auch in Node.js ab v6.0 vollständig unterstützt. Eine ausführliche Browserkompatibilitätsliste finden Sie in der MDN-Dokumentation und unter Node auf der Website node.green .

Generatorfunktionen

Eine Generatorfunktion wird mit einer function* . Wenn es aufgerufen wird, wird sein Körper nicht sofort ausgeführt. Stattdessen wird ein Generatorobjekt zurückgegeben , mit dem die Ausführung der Funktion "schrittweise" durchlaufen werden kann.

Ein yield innerhalb des Funktionskörpers definiert einen Punkt, an dem die Ausführung ausgesetzt und fortgesetzt werden kann.

function* nums() {
    console.log('starting');  // A
    yield 1;                  // B
    console.log('yielded 1'); // C
    yield 2;                  // D
    console.log('yielded 2'); // E
    yield 3;                  // F
    console.log('yielded 3'); // G
}
var generator = nums(); // Returns the iterator. No code in nums is executed

generator.next();  // Executes lines A,B returning { value: 1, done: false }
// console: "starting"
generator.next();  // Executes lines C,D returning { value: 2, done: false }
// console: "yielded 1"
generator.next();  // Executes lines E,F returning { value: 3, done: false }
// console: "yielded 2"
generator.next();  // Executes line G returning { value: undefined, done: true }
// console: "yielded 3"

Vorzeitige Iteration beenden

generator = nums();
generator.next(); // Executes lines A,B returning { value: 1, done: false }
generator.next(); // Executes lines C,D returning { value: 2, done: false }
generator.return(3); // no code is executed returns { value: 3, done: true }
// any further calls will return done = true 
generator.next(); // no code executed returns { value: undefined, done: true }

Fehler an Generatorfunktion

function* nums() {
    try {
        yield 1;                  // A
        yield 2;                  // B
        yield 3;                  // C
    } catch (e) {
        console.log(e.message);    // D
    }
}

var generator = nums();

generator.next();  // Executes line A returning { value: 1, done: false }
generator.next();  // Executes line B returning { value: 2, done: false }
generator.throw(new Error("Error!!"));  // Executes line D returning { value: undefined, done: true}
// console: "Error!!"
generator.next();  // no code executed. returns { value: undefined, done: true }

Iteration

Ein Generator ist iterierbar . Es kann mit einer for...of Anweisung durchlaufen werden und in anderen Konstrukten verwendet werden, die vom Iterationsprotokoll abhängen.

function* range(n) {
    for (let i = 0; i < n; ++i) {
        yield i;
    }
}

// looping
for (let n of range(10)) {
    // n takes on the values 0, 1, ... 9
}

// spread operator
let nums = [...range(3)];  // [0, 1, 2]
let max = Math.max(...range(100));  // 99

Hier ist ein weiteres Beispiel für den Verwendungsgenerator für benutzerdefinierte iterierbare Objekte in ES6. Hier wird eine anonyme Generatorfunktion function * verwendet.

let user = {
  name: "sam", totalReplies: 17, isBlocked: false
};

user[Symbol.iterator] = function *(){

  let properties = Object.keys(this);
  let count = 0;
  let isDone = false;

  for(let p of properties){
    yield this[p];
  }
};

for(let p of user){
  console.log( p );
} 

Werte an Generator senden

Es ist möglich , einen Wert an den Generator zu senden, indem Sie ihn an die next() Methode übergeben.

function* summer() {
    let sum = 0, value;
    while (true) {
        // receive sent value
        value = yield;
        if (value === null) break;

        // aggregate values
        sum += value;
    }
    return sum;
}
let generator = summer();

// proceed until the first "yield" expression, ignoring the "value" argument
generator.next();

// from this point on, the generator aggregates values until we send "null"
generator.next(1);
generator.next(10);
generator.next(100);

// close the generator and collect the result
let sum = generator.next(null).value;  // 111

Delegieren an andere Generator

Innerhalb einer Generatorfunktion kann die Steuerung mithilfe von yield* an eine andere Generatorfunktion delegiert werden.

function* g1() {
  yield 2;
  yield 3;
  yield 4;
}

function* g2() {
  yield 1;
  yield* g1();
  yield 5;
}

var it = g2();

console.log(it.next()); // 1
console.log(it.next()); // 2
console.log(it.next()); // 3
console.log(it.next()); // 4
console.log(it.next()); // 5
console.log(it.next()); // undefined

Iterator-Observer-Schnittstelle

Ein Generator ist eine Kombination aus zwei Dingen - einem Iterator und einem Observer .

Iterator

Ein Iterator ist etwas , wenn ein Aufruf zurückgibt iterable . Ein iterable ist etwas, das Sie durchlaufen können. Ab ES6 / ES2015 stimmen alle Sammlungen (Array, Map, Set, WeakMap, WeakSet) mit dem Iterable-Vertrag überein.

Ein Generator (Iterator) ist ein Produzent. In der Iteration PULL der Verbraucher den Wert des Herstellers an.

Beispiel:

function *gen() { yield 5; yield 6; }
let a = gen();

Jedes Mal , wenn Sie anrufen a.next() , sind Sie im Wesentlichen pull Wert aus dem Iterator -ing und pause die Ausführung an yield . Beim nächsten Aufruf von a.next() wird die Ausführung aus dem zuvor angehaltenen Zustand a.next() .

Beobachter

Ein Generator ist auch ein Beobachter, mit dem Sie einige Werte in den Generator zurückschicken können.

function *gen() {
  document.write('<br>observer:', yield 1);
}
var a = gen();
var i = a.next();
while(!i.done) {
  document.write('<br>iterator:', i.value);
  i = a.next(100);
}

Hier können Sie sehen, dass yield 1 wie ein Ausdruck verwendet wird, der einen bestimmten Wert ergibt. Der Wert, für den es ausgewertet wird, ist der Wert, der als Argument an den Funktionsaufruf a.next wird.

Zum ersten Mal ist i.value der erste erhaltene Wert ( 1 ), und wenn die Iteration mit dem nächsten Zustand fortgesetzt wird, senden wir einen Wert mit a.next(100) an den Generator zurück.

Asynchron mit Generatoren machen

Generatoren werden häufig mit der spawn (von taskJS oder co) verwendet, bei der die Funktion einen Generator aufnimmt und es uns ermöglicht, asynchronen Code synchron zu schreiben. Dies bedeutet NICHT, dass asynchroner Code in synchronen Code umgewandelt / synchron ausgeführt wird. Das bedeutet, dass wir Code schreiben können, der wie sync aussieht, intern jedoch immer noch async .

Sync ist BLOCKEN; Async wartet. Das Schreiben von Code, der blockiert, ist einfach. Beim PULLing erscheint der Wert in der Zuordnungsposition. Beim PUSHing wird der Wert an der Argumentposition des Rückrufs angezeigt.

Wenn Sie Iteratoren verwenden, PULL Sie den Wert des Herstellers ab. Wenn Sie Rückrufe verwenden, der Produzent PUSH ES den Wert auf die Argumentposition des Callback.

var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH

Hier legen Sie den Wert aus dem Pull - a.next() und in der zweiten, v => {...} ist der Rückruf und ein Wert ist PUSH ed in die Argumentposition v der Callback - Funktion.

Mit diesem Pull-Push-Mechanismus können wir eine asynchrone Programmierung wie folgt schreiben:

let delay = t => new Promise(r => setTimeout(r, t));
spawn(function*() {
  // wait for 100 ms and send 1
  let x = yield delay(100).then(() => 1);
  console.log(x); // 1

   // wait for 100 ms and send 2
  let y = yield delay(100).then(() => 2);
  console.log(y); // 2
});

Wenn Sie sich den obigen Code ansehen, schreiben wir asynchronen Code, der aussieht, als ob er blocking (die Yield-Anweisungen warten auf 100 ms und setzen dann die Ausführung fort), aber es waiting tatsächlich. Die pause und resume Eigenschaft des Generators ermöglicht uns diesen erstaunlichen Trick.

Wie funktioniert es ?

Die Spawn-Funktion verwendet ein yield promise um den Versprechen-Status vom Generator zu ZIEHEN, wartet, bis das Versprechen gelöst ist, und PUSHes den aufgelösten Wert zum Generator zurück, damit er es verbrauchen kann.

Benutze es jetzt

Mit Generatoren und Spawn-Funktionen können Sie also Ihren gesamten asynchronen Code in NodeJS bereinigen, um das Aussehen und das Gefühl zu haben, als wäre es synchron. Dies erleichtert das Debuggen. Auch der Code wird ordentlich aussehen.

Diese Funktion wird für zukünftige Versionen von JavaScript - async...await . Sie können sie jedoch heute in ES2015 / ES6 verwenden, indem Sie die in den Bibliotheken definierte Spawn-Funktion verwenden - taskjs, co oder bluebird

Asynchroner Fluss mit Generatoren

Generatoren sind Funktionen, die die Ausführung unterbrechen und wieder aufnehmen können. Dies ermöglicht die Emulation asynchroner Funktionen mit externen Bibliotheken, hauptsächlich q oder co. Grundsätzlich können Funktionen geschrieben werden, die auf asynchrone Ergebnisse warten, um fortzufahren:

function someAsyncResult() {
    return Promise.resolve('newValue')
}

q.spawn(function * () {
    var result = yield someAsyncResult()
    console.log(result) // 'newValue'
})

Dies erlaubt es, asynchronen Code so zu schreiben, als wäre er synchron. Versuchen Sie außerdem, die Arbeit über mehrere asynchrone Blöcke zu fangen. Wenn das Versprechen abgelehnt wird, wird der Fehler vom nächsten Fang abgefangen:

function asyncError() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            reject(new Error('Something went wrong'))
        }, 100)
    })
}

q.spawn(function * () {
    try {
        var result = yield asyncError()
    } catch (e) {
        console.error(e) // Something went wrong
    }
})

Die Verwendung von co würde genauso funktionieren, jedoch mit co(function * (){...}) anstelle von q.spawn



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow