Ricerca…


introduzione

Le funzioni del generatore (definite dalla function* parola chiave) funzionano come coroutine, generando una serie di valori quando vengono richiesti attraverso un iteratore.

Sintassi

  • funzione * nome (parametri) {valore di rendimento; valore di ritorno }
  • generatore = nome (argomenti)
  • {value, done} = generator.next (valore)
  • {value, done} = generator.return (valore)
  • generator.throw (errore)

Osservazioni

Le funzioni del generatore sono una funzionalità introdotta come parte delle specifiche ES 2015 e non sono disponibili in tutti i browser. Inoltre sono completamente supportati in Node.js dalla v6.0 . Per un elenco dettagliato di compatibilità del browser, consultare la documentazione MDN e per il nodo, consultare il sito Web node.green .

Funzioni del generatore

Una funzione generatore viene creata con una dichiarazione di function* . Quando viene chiamato, il suo corpo non viene immediatamente eseguito. Invece, restituisce un oggetto generatore , che può essere utilizzato per "passare attraverso" l'esecuzione della funzione.

Un'espressione di yield all'interno del corpo della funzione definisce un punto in cui l'esecuzione può sospendere e riprendere.

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"

Uscita di iterazione anticipata

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 }

Lancio di un errore nella funzione del generatore

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 }

Iterazione

Un generatore è iterabile . Può essere ripetuto con una for...of istruzione e utilizzato in altri costrutti che dipendono dal protocollo di iterazione.

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

Ecco un altro esempio di generatore di utilizzo per oggetto iterabile personalizzato in ES6. Qui è utilizzata la funzione function * generatore anonimo 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 );
} 

Invio dei valori al generatore

È possibile inviare un valore al generatore passando al metodo 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

Delega ad altro generatore

Dall'interno di una funzione generatore, il controllo può essere delegato a un'altra funzione del generatore usando 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

Interfaccia Iterator-Observer

Un generatore è una combinazione di due cose: un Iterator e un Observer .

Iterator

Un iteratore è qualcosa quando invocato restituisce un iterable . Un iterable è qualcosa su cui è possibile iterare. Da ES6 / ES2015 in poi, tutte le raccolte (Array, Map, Set, WeakMap, WeakSet) sono conformi al contratto Iterable.

Un generatore (iteratore) è un produttore. In iterazione il consumatore PULL il valore dal produttore.

Esempio:

function *gen() { yield 5; yield 6; }
let a = gen();

Ogni volta che si chiama a.next() , si sta essenzialmente pull valore da Iterator e si pause l'esecuzione a yield . La prossima volta che chiamate a.next() , l'esecuzione riprende dallo stato precedentemente sospeso.

Osservatore

Un generatore è anche un osservatore con cui è possibile inviare alcuni valori nel generatore.

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);
}

Qui puoi vedere che la yield 1 è usata come un'espressione che valuta un certo valore. Il valore che valuta è il valore inviato come argomento alla chiamata della funzione a.next .

Quindi, per la prima volta i.value sarà il primo valore restituito ( 1 ), e quando si continua l'iterazione allo stato successivo, inviamo un valore al generatore usando a.next(100) .

Fare asincrono con i generatori

I generatori sono ampiamente usati con la funzione spawn (da taskJS o co), dove la funzione accetta un generatore e ci consente di scrivere codice asincrono in modo sincrono. Questo NON significa che il codice asincrono viene convertito in codice di sincronizzazione / eseguito in modo sincrono. Significa che possiamo scrivere codice simile alla sync ma internamente è ancora async .

La sincronizzazione è BLOCCO; Async sta aspettando. Scrivere il codice che blocca è facile. Quando si esegue il PULLing, il valore appare nella posizione di assegnazione. Quando PUSHing, il valore appare nella posizione dell'argomento del callback.

Quando si utilizzano gli iteratori, si PULL il valore dal produttore. Quando si usano i callback, il produttore PUSH il valore alla posizione dell'argomento del callback.

var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH

Qui, si tira il valore da a.next() e nel secondo, v => {...} è il callback e un valore è PUSH ed nella posizione dell'argomento v della funzione di callback.

Usando questo meccanismo pull-push, possiamo scrivere una programmazione asincrona come questa,

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
});

Quindi, guardando il codice di cui sopra, stiamo scrivendo un codice asincrono che sembra blocking (le dichiarazioni di rendimento aspettano 100 ms e poi continuano l'esecuzione), ma in realtà sta waiting . La proprietà di pause e resume del generatore ci consente di fare questo straordinario trucco.

Come funziona ?

La funzione di spawn utilizza la yield promise per PULL lo stato di promessa dal generatore, attende che la promessa sia risolta e PUSH il valore risolto indietro al generatore in modo che possa consumarlo.

Usalo ora

Quindi, con i generatori e la funzione di spawn, è possibile ripulire tutto il codice asincrono in NodeJS in modo da sembrare sincronizzato. Questo renderà il debug facile. Anche il codice apparirà pulito.

Questa funzione sta arrivando a versioni future di JavaScript - async...await . Ma puoi usarli oggi in ES2015 / ES6 usando la funzione di spawn definita nelle librerie - taskjs, co o bluebird

Flusso asincrono con generatori

I generatori sono funzioni in grado di mettere in pausa e quindi riprendere l'esecuzione. Questo permette di emulare funzioni asincrone usando librerie esterne, principalmente q o co. Fondamentalmente consente di scrivere funzioni che attendono risultati asincroni per andare avanti:

function someAsyncResult() {
    return Promise.resolve('newValue')
}

q.spawn(function * () {
    var result = yield someAsyncResult()
    console.log(result) // 'newValue'
})

Ciò consente di scrivere codice asincrono come se fosse sincrono. Inoltre, prova a catturare il lavoro su diversi blocchi asincroni. Se la promessa viene respinta, l'errore viene catturato dalla prossima cattura:

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
    }
})

Usare co funzionerebbe esattamente allo stesso modo ma con co(function * (){...}) invece di q.spawn



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow