Recherche…
Introduction
Les fonctions de générateur (définies par le mot-clé function*
) s'exécutent comme des coroutines, générant une série de valeurs telles qu'elles sont demandées via un itérateur.
Syntaxe
- fonction * nom (paramètres) {valeur de rendement; valeur de retour}
- generator = name (arguments)
- {value, done} = generator.next (valeur)
- {value, done} = generator.return (value)
- générateur.throw (erreur)
Remarques
Les fonctions de générateur sont une fonctionnalité introduite dans la spécification ES 2015 et ne sont pas disponibles dans tous les navigateurs. Ils sont également entièrement pris en charge dans Node.js à partir de la v6.0
. Pour une liste de compatibilité détaillée du navigateur, consultez la documentation MDN et, pour Node, consultez le site Web node.green .
Fonctions du générateur
Une fonction générateur est créée avec une déclaration de function*
. Lorsqu'il est appelé, son corps n'est pas exécuté immédiatement. Au lieu de cela, il retourne un objet générateur , qui peut être utilisé pour "parcourir" l'exécution de la fonction.
Une expression de yield
dans le corps de la fonction définit un point auquel l'exécution peut être suspendue et reprise.
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"
Sortie itération précoce
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 }
Lancer une erreur sur la fonction du générateur
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 }
Itération
Un générateur est itérable . Il peut être bouclé avec une instruction for...of
et utilisé dans d'autres constructions qui dépendent du protocole d'itération.
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
Voici un autre exemple de générateur d'utilisation pour personnaliser les objets itérables dans ES6. Ici , la fonction de générateur anonyme function *
utilisé.
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 );
}
Envoi de valeurs au générateur
Il est possible d' envoyer une valeur au générateur en la passant à la méthode 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
Délégation à un autre générateur
A partir d'une fonction de générateur, le contrôle peut être délégué à une autre fonction de générateur en utilisant le 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
Interface Iterator-Observer
Un générateur est une combinaison de deux choses - un Iterator
et un Observer
.
Itérateur
Un itérateur est quelque chose quand invoqué renvoie une iterable
. Une iterable
est quelque chose que vous pouvez parcourir. A partir de ES6 / ES2015, toutes les collections (Array, Map, Set, WeakMap, WeakSet) sont conformes au contrat Iterable.
Un générateur (itérateur) est un producteur. En itération, le consommateur
PULL
la valeur du producteur.
Exemple:
function *gen() { yield 5; yield 6; }
let a = gen();
Chaque fois que vous appelez a.next()
, vous pull
essentiellement la valeur de l'itérateur et mettez en pause
l'exécution au yield
. La prochaine fois que vous appelez a.next()
, l'exécution reprend à partir de l'état précédemment suspendu.
Observateur
Un générateur est également un observateur à l'aide duquel vous pouvez renvoyer certaines valeurs dans le générateur.
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);
}
Ici, vous pouvez voir que le yield 1
est utilisé comme une expression qui donne une valeur. La valeur à laquelle il évalue est la valeur envoyée en tant qu'argument à l' a.next
fonction a.next
.
Donc, pour la première fois, i.value
sera la première valeur i.value
( 1
), et en continuant l'itération à l'état suivant, nous a.next(100)
une valeur au générateur en utilisant a.next(100)
.
Faire des asynchrones avec des générateurs
Les générateurs sont largement utilisés avec la fonction spawn
(from taskJS ou co), où la fonction prend un générateur et permet d’écrire du code asynchrone de manière synchrone. Cela ne signifie PAS que le code asynchrone est converti en code de synchronisation / exécuté de manière synchrone. Cela signifie que nous pouvons écrire du code qui ressemble à la sync
mais en interne, il reste toujours async
.
La synchronisation bloque; Async est en attente. Ecrire du code qui bloque est facile. Lorsque PULLing, la valeur apparaît dans la position d'affectation. Lorsque PUSHing, la valeur apparaît dans la position de l'argument du rappel.
Lorsque vous utilisez des itérateurs, vous PULL
la valeur du producteur. Lorsque vous utilisez des rappels, le producteur PUSH
évalue la valeur à la position d'argument du rappel.
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Ici, vous a.next()
la valeur de a.next()
et dans la seconde v => {...}
est le rappel et une valeur est PUSH
ed dans la position d'argument v
de la fonction de rappel.
En utilisant ce mécanisme push-push, nous pouvons écrire une programmation asynchrone comme celle-ci,
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
});
Donc, en regardant le code ci-dessus, nous écrivons un code asynchrone qui ressemble à un blocking
(les instructions de rendement attendent pendant 100 ms puis continuent leur exécution), mais elles waiting
réellement. La pause
et la propriété de resume
du générateur nous permettent de faire ce tour incroyable.
Comment ça marche ?
La fonction spawn utilise la yield promise
pour PULL l'état de promesse du générateur, attend que la promesse soit résolue et PUSHes la valeur résolue au générateur pour qu'il puisse la consommer.
Utilise le maintenant
Donc, avec les générateurs et la fonction spawn, vous pouvez nettoyer tout votre code asynchrone dans NodeJS pour avoir l’impression qu’il est synchrone. Cela facilitera le débogage. De plus, le code sera soigné.
Cette fonctionnalité arrive dans les futures versions de JavaScript - comme async...await
. Mais vous pouvez les utiliser aujourd'hui dans ES2015 / ES6 en utilisant la fonction spawn définie dans les bibliothèques - taskjs, co ou bluebird
Flux asynchrone avec générateurs
Les générateurs sont des fonctions capables de faire une pause puis de reprendre l'exécution. Cela permet d'émuler des fonctions asynchrones à l'aide de bibliothèques externes, principalement q ou co. Fondamentalement, il permet d'écrire des fonctions qui attendent des résultats asynchrones pour continuer:
function someAsyncResult() {
return Promise.resolve('newValue')
}
q.spawn(function * () {
var result = yield someAsyncResult()
console.log(result) // 'newValue'
})
Cela permet d'écrire du code asynchrone comme s'il était synchrone. De plus, essayez de récupérer plusieurs blocs asynchrones. Si la promesse est rejetée, l'erreur est détectée par la capture suivante:
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
}
})
Utiliser co fonctionnerait exactement de la même façon mais avec co(function * (){...})
au lieu de q.spawn