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 dedb.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.
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.
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 yield
ed 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é.
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.