Sök…
Introduktion
Generatorfunktioner (definierade av function*
nyckelord) körs som koroutiner och genererar en serie värden som de begärs genom en iterator.
Syntax
- funktion * namn (parametrar) {avkastningsvärde; returvärde }
- generator = namn (argument)
- {värde, gjort} = generator.nästa (värde)
- {värde, gjort} = generator.return (värde)
- generator.throw (fel)
Anmärkningar
Generatorfunktioner är en funktion som introduceras som en del av ES 2015-specifikationen och är inte tillgänglig i alla webbläsare. De stöds också helt i Node.js från v6.0
. För en detaljerad webbläsarkompatibilitetslista, se MDN-dokumentationen , och för Node, se node.green- webbplatsen.
Generatorfunktioner
En generatorfunktion skapas med en function*
-deklaration. När det kallas körs inte kroppen direkt. Istället returnerar det ett generatorobjekt som kan användas för att "kliva igenom" funktionens körning.
Ett yield
inuti funktionskroppen definierar en punkt vid vilken exekvering kan avbryta och återuppta.
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"
Tidig iterationsutgång
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 }
Kasta ett fel till generatorfunktionen
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
En generator är iterable . Det kan slingas med en for...of
uttalande och användas i andra konstruktioner som beror på iterationsprotokollet.
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
Här är ett annat exempel på användningsgenerator för att anpassa iterable-objekt i ES6. Här används anonym funktionsfunktion function *
.
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 );
}
Skicka värden till generator
Det är möjligt att skicka ett värde till generatorn genom att skicka det till next()
-metod.
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
Delegera till andra generatorer
Från en generatorfunktion kan styrningen delegeras till en annan generatorfunktion med 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-gränssnitt
En generator är en kombination av två saker - en Iterator
och en Observer
.
iterator
En iterator är något när den åberopas returnerar en iterable
. En iterable
är något du kan iterera på. Från ES6 / ES2015 och framåt överensstämmer alla samlingar (Array, Map, Set, WeakMap, WeakSet) med det Iterable-kontraktet.
En generator (iterator) är en producent. I iteration konsumenten
PULL
s värdet från tillverkaren.
Exempel:
function *gen() { yield 5; yield 6; }
let a = gen();
När du ringer a.next()
pull
du i huvudsak värdet från Iterator och pause
exekveringen på yield
. Nästa gång du ringer a.next()
återupptas exekveringen från det tidigare pausade tillståndet.
Observatör
En generator är också en observatör där du kan skicka några värden tillbaka till generatorn.
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);
}
Här kan du se att yield 1
används som ett uttryck som utvärderar till ett värde. Värdet den utvärderar till är det värde som skickas som ett argument till a.next
funktionssamtalet.
Så för första gången kommer i.value
att vara det första värdet som ges ( 1
), och när du fortsätter iterationen till nästa tillstånd skickar vi ett värde tillbaka till generatorn med hjälp av a.next(100)
.
Gör async med generatorer
Generatorer används ofta med spawn
(från taskJS eller co) -funktion, där funktionen tar in en generator och gör att vi kan skriva asynkron kod på ett synkront sätt. Detta betyder INTE att asynkodskod konverteras till synkroniseringskod / körs synkront. Det betyder att vi kan skriva kod som ser ut som sync
men internt är den fortfarande async
.
Synkroniseringen är BLOCKING; Async väntar. Det är enkelt att skriva kod som blockerar. När PULLing visas visas värdet i tilldelningspositionen. När PUSHing visas värde i argumentläget för återuppringningen.
När du använder iteratorer, PULL
värdet från producenten. När du använder återuppringningar visar producenten PUSH
värdet för återuppringningens argumentposition.
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Här drar du värdet från a.next()
och i det andra är v => {...}
återuppringning och ett värde PUSH
ed till argumentläget v
för återuppringningsfunktionen.
Med hjälp av denna pull-push-mekanism kan vi skriva async-programmering som denna,
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
});
Så när vi tittar på koden ovan skriver vi async-kod som ser ut som om den blocking
(avkastningsuppgifterna väntar på 100 ms och fortsätter sedan körningen), men den waiting
faktiskt. Generatorens pause
och resume
tillåter oss att göra detta fantastiska trick.
Hur fungerar det ?
Spawn-funktionen använder yield promise
att PULLa löfte-tillståndet från generatorn, väntar tills löften har lösts och PUSHes det upplösta värdet tillbaka till generatorn så att den kan konsumera det.
Använd det nu
Så med generatorer och spawn-funktion kan du städa upp all din async-kod i NodeJS för att se ut och känna att den är synkron. Detta kommer att göra felsökning lätt. Koden kommer också att se snygg ut.
Denna funktion kommer till kommande versioner av JavaScript - som async...await
. Men du kan använda dem idag i ES2015 / ES6 med hjälp av spawn-funktionen som definieras i biblioteken - taskjs, co eller bluebird
Asyncflöde med generatorer
Generatorer är funktioner som kan pausa och sedan fortsätta körningen. Detta gör det möjligt att emulera async-funktioner med hjälp av externa bibliotek, huvudsakligen q eller co. I grunden tillåter det att skriva funktioner som väntar på asynkresultat för att fortsätta:
function someAsyncResult() {
return Promise.resolve('newValue')
}
q.spawn(function * () {
var result = yield someAsyncResult()
console.log(result) // 'newValue'
})
Detta gör det möjligt att skriva async-kod som om den var synkron. Försök dessutom att fånga arbete över flera asyncblock. Om löfte avvisas fångas felet av nästa fångst:
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
}
})
Att använda co skulle fungera exakt samma men med co(function * (){...})
istället för q.spawn