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