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