Générateurs Javascript: les comprendre

je suis presque sûr que ma compréhension des générateurs est intrinsèquement brisée. Toutes les ressources en ligne semblent entrer en conflit et cela rend l'expérience d'apprentissage incroyablement difficile et déroutante.

D'après ce que j'ai compris, le yield keyword permet à un bloc de code en cours d'exécution de attente pour une valeur au lieu de lancer le code restant à exécuter à l'intérieur d'un callback. Donc, comme la plupart des tutoriels ont souligné, vous pouvez utiliser ceci:

(function *() {
     // Wait until users have be got and put into value of `results`
     var results = yield db.get("users");
     // And continue
     view.display(results);
})();

au Lieu de:

db.get("user", function(results) {
    view.display(results);
});

c'est vrai, tout va bien jusqu'à ce que j'essaie d'écrire mes propres générateurs. J'ai couru dans plusieurs empêchements:

  • le premier exemple de code I ci-dessus ne s'exécute pas parce qu'il n'y a rien à itérer sur le générateur, correct? Certains de supérieur doit appeler l' .next quelque part, non?
  • l'API entière devra être réécrite jusqu'aux appels d'entrée/sortie pour prendre en charge les générateurs, - il correct?
  • D'après ce que j'ai compris,yield semble attendez que la valeur la plupart des cas d'utilisation générale tandis que dans la partie implémentation (lire: retourner la valeur à / à l'intérieur de db.get) yield semble renvoie cette valeur au bloc en attente pour reprendre l'exécution.

par exemple:

function *fn() {
    yield 1;
    yield "a";
}

var gen = fn();
gen.next(); // 1
gen.next(); // "a";

yield dans ce contexte renvoie des valeurs vers le bas au lieu d'attendre les résultats. Dans le premier exemple ci-dessus, il attend les résultats de l' db.get et l'exécution reprend au lieu de" retourner " ou de renvoyer une valeur. Si le db.get cas est vrai, n'est-ce pas intrinsèquement synchrone? Je veux dire, n'est-ce pas exactement la même chose que:

(function() {
     //Wait for the results
    var results = fs.readFileSync("users.txt");
    // Use results
    view.display(results);
})();

malheureusement, si cette question est claire (probablement la seule chose claire), c'est que je ne comprends pas les générateurs. Avec un peu de chance, je pourrais avoir un aperçu ici.

19
demandé sur AdrianCooney 2013-12-25 07:59:50

3 réponses

TL;DR: l'essence du générateur est contrôle de la suspension de l'exécution du code.

Pour le générateur lui-même, vous pouvez vous référer à .

Pour résumer, il y a trois éléments que vous devriez distinguer: 1. générateur de fonction 2. générateur 3. résultat généré

la fonction Generator est simplement le function avec l'étoile dans sa tête et (facultatif) yield dans son corps.

function *generator() {
  console.log('Start!');
  var i = 0;
  while (true) {
    if (i < 3)
      yield i++;
  }
}

var gen = generator();
// nothing happens here!!

fonction génératrice elle-même ne fait rien d'autre que de retourner un générateur, dans le cas ci-dessus, gen. Pas de sortie de la console ici car seulement après le retour générateurnext méthode est appelée le corps de générateur de fonction va exécuter. Générateur dispose de plusieurs méthodes, dont la plus importante est next. next exécute le code et renvoie l' générateur de résultat.

var ret = gen.next();
// Start!
console.log(ret);
// {value: 0, done: false}

ret ci-dessus est générateur de résultat. Il dispose de deux propriété: value, la valeur que vous donnez en générateur de fonction et done, un drapeau indiquant si le générateur de fonction retour.

console.log(gen.next());
// {value: 1, done: false}
console.log(gen.next());
// {value: 2, done: false}
console.log(gen.next());
// {value: undefined, done: true}

À ce stade, personne n'attendent que vous pour comprendre générateur, du moins, pas asynchrone de puissance du générateur.

pour simplifier, generator a deux caractéristiques:

  • on peut choisir de sauter hors d'une fonction et laisser le code extérieur pour déterminer quand revenir dans fonction.
  • le contrôle de l'appel asynchrone peut être fait en dehors de votre code

en code,yield saute en dehors de la fonction, et next(val) revient à la fonction et renvoie la valeur à la fonction. Le code extérieur peut gérer l'appel asynchrone et décider du moment approprié pour passer à votre propre code.

Regardez l'exemple de nouveau:

var gen = generator();
console.log('generated generator');
console.log(gen.next().value);
// mock long long processing
setTimeout(function() {
  console.log(gen.next().value);
  console.log('Execute after timer fire');
}, 1000);
console.log('Execute after timer set');

/* result:
    generated generator
    start
    0
    Execute after timer set
    1
    Execute after timer fire
*/

Voir? La fonction generator elle-même ne gère pas le rappel. Le code extérieur faire.

La base est ici. Vous pouvez élaborer ce code pour soutenir l'asynchronisme complet tout en gardant la fonction de générateur comme sync one.

Par exemple, supposons que geturl est un appel asynchrone qui renvoie un promise objet. vous pouvez écrire var html = yield getUrl('www.stackoverflow.com'); ceci dépasse votre code. Et à l'extérieur de code va faire des trucs comme:

var ret = gen.next();
ret.then(function (fetchedHTML) {
  // jumps back to your generator function
  // and assign fetchHTML to html in your code
  gen.next(fetchedHTML);
});

pour un guide plus complet, voir . Et le référentiel de la forme co, galaxy,suspendre et etc.

21
répondu Herrington Darkholme 2016-10-04 07:46:21

aucune de ces choses asynchrones si elles font partie des générateurs. les générateurs mettent tout simplement en pause et reprennent des blocs de code. toute la magie asynchrone se produit quand vous utilisez ce que j'appelle un "moteur de générateur" comme https://github.com/visionmedia/co.

fondamentalement, ce que gen.next() n'est le retour de la dernière yielded valeur et vous permettent de retourner une valeur si le yield est assigné à quelque chose, ex. var something = yield 1. donc, si vous avez le bloc de code suivant:

function* genfun() {
  var a = yield 1
  var b = yield 2
}

var gen = genfun()

gen.next() // returns the first yielded value via `{value: 1}`
gen.next(1) // sets `a` as 1, returns the next yielded value via `{value: 2}`
gen.next(2) // sets `b` as 2, the generator is done, so it returns `{done: true}`

gen.throw(err) est la même comme suivant, sauf que l'erreur est lancée au lieu d'être assignée à une variable.

voici comment fonctionnent les moteurs de contrôle de flux - vous obtenez la valeur suivante qui est probablement un rappel ou quelque chose. exécutez le rappel et ne pas gen.next() jusqu'à ce que le rappel est terminé.

3
répondu Jonathan Ong 2013-12-25 06:34:37

Les deux exemples ne sont pas les mêmes. Lorsque vous cédez, la fonction devient effectivement un callback, attendant d'être exécutée lorsque db.obtenir ("utilisateurs") finis. De cette façon, la fonction ne bloque pas l'exécution des autres fonctions. Pensez - y comme un moyen de transformer les fonctions synchrones en fonctions asynchrones en faisant savoir au système que vous pouvez faire une pause à certains moments.

1
répondu leorex 2013-12-25 06:17:22