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



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow