Zoeken…
Invoering
Generatorfuncties (gedefinieerd door het trefwoord function*
) worden uitgevoerd als coroutines en genereren een reeks waarden wanneer deze via een iterator worden opgevraagd.
Syntaxis
- functie * naam (parameters) {opbrengstwaarde; winstwaarde }
- generator = naam (argumenten)
- {value, done} = generator.next (waarde)
- {value, done} = generator.return (waarde)
- generator.throw (fout)
Opmerkingen
Generatorfuncties zijn een functie die is geïntroduceerd als onderdeel van de ES 2015-specificatie en zijn niet in alle browsers beschikbaar. Ze worden ook volledig ondersteund in Node.js vanaf v6.0
. Zie de MDN-documentatie voor een gedetailleerde lijst met browsercompatibiliteit en voor Node de website node.green .
Generator Functies
Een generatorfunctie wordt gemaakt met een function*
-verklaring. Wanneer het wordt genoemd, wordt zijn lichaam niet onmiddellijk uitgevoerd. In plaats daarvan retourneert het een generatorobject , dat kan worden gebruikt om de uitvoering van de functie "door te lopen".
Een yield
in het functielichaam definieert een punt waarop de uitvoering kan worden onderbroken en hervat.
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"
Vroege iteratie-uitgang
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 }
Er is een fout opgetreden in de generatorfunctie
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 }
herhaling
Een generator is iterabel . Het kan worden herhaald met een for...of
-instructie en worden gebruikt in andere constructen die afhankelijk zijn van het iteratieprotocol.
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 is nog een voorbeeld van een gebruiksgenerator voor een aangepast itereerbaar object in ES6. Hier anonieme generator functie function *
gebruikt.
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 );
}
Waarden naar de generator verzenden
Het is mogelijk om een waarde naar de generator te sturen door deze door te geven aan de methode next()
.
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
Delegeren naar andere generator
Vanuit een generatorfunctie kan de besturing worden overgedragen aan een andere generatorfunctie met behulp van yield*
.
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-interface
Een generator is een combinatie van twee dingen - een Iterator
en een Observer
.
iterator
Een iterator is iets wanneer aangeroepen een iterable
. Een iterable
is iets dat je kunt herhalen. Vanaf ES6 / ES2015 voldoen alle collecties (Array, Map, Set, WeakMap, WeakSet) aan het Iterable-contract.
Een generator (iterator) is een producent. In iteratie de consument
PULL
is de waarde van de producent.
Voorbeeld:
function *gen() { yield 5; yield 6; }
let a = gen();
Wanneer je belt a.next()
, bent u in feite pull
ing waarde uit de Iterator en pause
de uitvoering op yield
. De volgende keer dat u a.next()
, wordt de uitvoering hervat vanuit de eerder gepauzeerde status.
Waarnemer
Een generator is ook een waarnemer waarmee u enkele waarden terug naar de generator kunt sturen.
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 kunt u zien dat yield 1
wordt gebruikt als een uitdrukking die tot een bepaalde waarde leidt. De waarde a.next
wordt geëvalueerd, is de waarde die als argument naar de a.next
functie-aanroep wordt verzonden.
Dus voor de eerste keer is i.value
de eerste waarde die wordt opgebracht ( 1
) en wanneer we doorgaan met de iteratie naar de volgende status, sturen we een waarde terug naar de generator met a.next(100)
.
Asynchroniseren met generatoren
Generatoren worden veel gebruikt met de spawn
(van taskJS of co), waarbij de functie een generator opneemt en ons in staat stelt asynchrone code op een synchrone manier te schrijven. Dit betekent NIET dat asynchronisatiecode wordt geconverteerd naar synchronisatiecode / synchroon wordt uitgevoerd. Het betekent dat we code kunnen schrijven die lijkt op sync
maar intern is deze nog steeds async
.
Synchronisatie is BLOKKEREN; Async WACHT. Het schrijven van code die blokkeert is eenvoudig. Tijdens het PULLEN verschijnt de waarde op de toewijzingspositie. Bij PUSHing verschijnt waarde in de argumentpositie van de callback.
Wanneer u iterators gebruiken, moet u PULL
de waarde van de producent. Wanneer u callbacks gebruikt, geeft de producent PUSH
de waarde aan de argumentpositie van de callback.
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Hier trekt u de waarde uit a.next()
en in de tweede is v => {...}
de callback en wordt een waarde PUSH
ed naar de argumentpositie v
van de callback-functie.
Met behulp van dit pull-push-mechanisme kunnen we async-programmering als volgt schrijven,
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
});
Dus als we naar de bovenstaande code kijken, schrijven we async-code die eruitziet alsof deze wordt blocking
(de opbrengstafspraken wachten 100 ms en gaan dan verder met de uitvoering), maar het waiting
eigenlijk. Met de pause
en resume
van de generator kunnen we deze geweldige truc doen.
Hoe werkt het ?
De spawn-functie gebruikt yield promise
om de beloftestatus van de generator te TREKKEN, wacht totdat de belofte is opgelost, en DRUKT de opgeloste waarde terug naar de generator zodat deze deze kan consumeren.
Gebruik het nu
Met generators en spawn-functie kunt u dus al uw async-code in NodeJS opschonen om eruit te zien en aan te voelen alsof het synchroon is. Dit maakt debuggen eenvoudig. Ook zal de code er netjes uitzien.
Deze functie komt naar toekomstige versies van JavaScript - async...await
. Maar je kunt ze vandaag gebruiken in ES2015 / ES6 met behulp van de spawn-functie die in de bibliotheken is gedefinieerd - taskjs, co of bluebird
Async-stroom met generatoren
Generators zijn functies die kunnen pauzeren en vervolgens de uitvoering kunnen hervatten. Hiermee kunt u asynchrone functies emuleren met externe bibliotheken, voornamelijk q of co. In principe kunt u functies schrijven die wachten op async-resultaten om verder te gaan:
function someAsyncResult() {
return Promise.resolve('newValue')
}
q.spawn(function * () {
var result = yield someAsyncResult()
console.log(result) // 'newValue'
})
Hiermee kan async-code worden geschreven alsof deze synchroon is. Probeer bovendien werk over verschillende async-blokken te vangen. Als de belofte wordt afgewezen, wordt de fout opgevangen door de volgende vangst:
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
}
})
Het gebruik van co zou precies hetzelfde werken, maar dan met co(function * (){...})
plaats van q.spawn