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



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow