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



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow