J'ai entendu parler d'un mot-clé "yield" en JavaScript, mais j'ai trouvé une documentation très pauvre à ce sujet. Quelqu'un peut-il m'expliquer (ou recommander un site qui explique) son utilisation et à quoi il sert?
J'ai entendu parler d'un mot-clé "yield" en JavaScript, mais j'ai trouvé une documentation très pauvre à ce sujet. Quelqu'un peut-il m'expliquer (ou recommander un site qui explique) son utilisation et à quoi il sert?
Réponses:
La documentation MDN est plutôt bonne, OMI.
La fonction contenant le mot-clé yield est un générateur. Lorsque vous l'appelez, ses paramètres formels sont liés à des arguments réels, mais son corps n'est pas réellement évalué. Au lieu de cela, un générateur-itérateur est renvoyé. Chaque appel à la méthode next () du générateur-itérateur effectue un autre passage à travers l'algorithme itératif. La valeur de chaque étape est la valeur spécifiée par le mot clé yield. Considérez le rendement comme la version générateur-itérateur du retour, indiquant la frontière entre chaque itération de l'algorithme. Chaque fois que vous appelez next (), le code du générateur reprend à partir de l'instruction suivant le rendement.
Réponse tardive, tout le monde est probablement au courant yield
maintenant, mais une meilleure documentation est arrivée.
Adaptation d'un exemple de "Javascript's Future: Generators" de James Long pour la norme officielle Harmony:
function * foo(x) {
while (true) {
x = x * 2;
yield x;
}
}
"Lorsque vous appelez foo, vous récupérez un objet Generator qui a une méthode suivante."
var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16
C'est un yield
peu comme return
: vous récupérez quelque chose. return x
renvoie la valeur de x
, mais yield x
renvoie une fonction, qui vous donne une méthode pour itérer vers la valeur suivante. Utile si vous disposez d'une procédure potentiellement gourmande en mémoire que vous souhaiterez peut-être interrompre pendant l'itération.
function* foo(x){
y êtes
*
. Que vous en ayez besoin ou non dépend du type d'avenir que vous revenez. Le détail est long: GvR l'explique pour l'implémentation Python , sur laquelle l'implémentation Javascript est modélisée. L'utilisation function *
sera toujours correcte, bien que dans certains cas, les frais généraux soient légèrement plus importants function
qu'avec yield
.
function *
et yield
et a ajouté l'erreur citée («Une erreur précoce est déclenchée si une expression de rendement ou de rendement * se produit dans une fonction non génératrice»). Mais, l'implémentation originale de Javascript 1.7 dans Firefox ne nécessitait pas le*
. Réponse mise à jour en conséquence. Merci!
C'est vraiment simple, c'est comme ça que ça marche
yield
Le mot clé permet simplement de suspendre et de reprendre une fonction à tout moment de manière asynchrone .Prenez cette fonction de générateur simple :
function* process() {
console.log('Start process 1');
console.log('Pause process2 until call next()');
yield;
console.log('Resumed process2');
console.log('Pause process3 until call next()');
let parms = yield {age: 12};
console.log("Passed by final process next(90): " + parms);
console.log('Resumed process3');
console.log('End of the process function');
}
let _process = process ();
Jusqu'à ce que vous appeliez _process.next (), il n'exécutera pas les 2 premières lignes de code, puis le premier rendement mettra la fonction en pause . Pour reprendre la fonction jusqu'au prochain point de pause ( mot-clé yield ), vous devez appeler _process.next () .
Vous pouvez penser que plusieurs rendements sont les points d'arrêt dans un débogueur javascript au sein d'une seule fonction. Jusqu'à ce que vous disiez de naviguer dans le prochain point d'arrêt, il n'exécutera pas le bloc de code. ( Remarque : sans bloquer toute l'application)
Mais alors que ce rendement effectue des comportements de pause et de reprendre il peut retourner des résultats aussi bien en {value: any, done: boolean}
fonction de la fonction précédente nous n'émettre aucune valeur. Si nous explorons la sortie précédente, elle affichera la même chose { value: undefined, done: false }
avec une valeur non définie .
Permet de creuser dans le mot clé yield. Facultativement, vous pouvez ajouter une expression et définir affecter une valeur facultative par défaut . (Syntaxe officielle du document)
[rv] = yield [expression];
expression : valeur à renvoyer de la fonction générateur
yield any;
yield {age: 12};
rv : renvoie la valeur facultative transmise à la méthode next () du générateur
Vous pouvez simplement passer des paramètres à la fonction process () avec ce mécanisme, pour exécuter différentes parties de rendement.
let val = yield 99;
_process.next(10);
now the val will be 10
Coutumes
Références:
Simplifiant / développant la réponse de Nick Sotiros (qui je pense est génial), je pense qu'il est préférable de décrire comment on pourrait commencer à coder yield
.
À mon avis, le plus grand avantage de l'utilisation yield
est qu'elle éliminera tous les problèmes de rappel imbriqués que nous voyons dans le code. Il est difficile de voir comment au début, c'est pourquoi j'ai décidé d'écrire cette réponse (pour moi, et j'espère que les autres!)
Pour ce faire, il introduit l'idée d'une co-routine, qui est une fonction qui peut s'arrêter / s'arrêter volontairement jusqu'à ce qu'elle obtienne ce dont elle a besoin. En javascript, cela est indiqué par function*
. Seules les function*
fonctions peuvent utiliser yield
.
Voici quelques javascript typiques:
loadFromDB('query', function (err, result) {
// Do something with the result or handle the error
})
C'est maladroit car maintenant tout votre code (qui doit évidemment attendre cet loadFromDB
appel) doit être à l'intérieur de ce rappel laid. C'est mauvais pour plusieurs raisons ...
})
que vous devez suivre partoutfunction (err, result)
jargon supplémentaireresult
D'un autre côté, avec yield
, tout cela peut être fait en une seule ligne à l'aide du joli cadre de co-routine.
function* main() {
var result = yield loadFromDB('query')
}
Et maintenant, votre fonction principale cédera si nécessaire quand elle devra attendre que les variables et les choses se chargent. Mais maintenant, pour exécuter cela, vous devez appeler une fonction normale (fonction non coroutine). Un cadre de co-routine simple peut résoudre ce problème de sorte que tout ce que vous avez à faire est d'exécuter ceci:
start(main())
Et le début est défini (d'après la réponse de Nick Sotiro)
function start(routine, data) {
result = routine.next(data);
if(!result.done) {
result.value(function(err, data) {
if(err) routine.throw(err); // continue next iteration of routine with an exception
else start(routine, data); // continue next iteration of routine normally
});
}
}
Et maintenant, vous pouvez avoir un beau code qui est beaucoup plus lisible, facile à supprimer et pas besoin de jouer avec les retraits, les fonctions, etc.
Une observation intéressante est que dans cet exemple, il yield
s'agit en fait d'un mot-clé que vous pouvez mettre avant une fonction avec un rappel.
function* main() {
console.log(yield function(cb) { cb(null, "Hello World") })
}
Imprime "Hello World". Ainsi, vous pouvez réellement transformer n'importe quelle fonction de rappel en utilisation yield
en créant simplement la même signature de fonction (sans le cb) et en retournant function (cb) {}
, comme ceci:
function yieldAsyncFunc(arg1, arg2) {
return function (cb) {
realAsyncFunc(arg1, arg2, cb)
}
}
Si tout va bien avec cette connaissance, vous pouvez écrire du code plus propre et plus lisible qui est facile à supprimer !
function*
est juste une fonction régulière sans rendement?
function *
c'est une fonction qui contient du rendement. C'est une fonction spéciale appelée générateur.
yield
partout, je suis sûr que cela a plus de sens que les rappels, mais je ne vois pas comment cela est plus lisible que les rappels.
Pour donner une réponse complète: yield
fonctionne de manière similaire return
, mais dans un générateur.
Quant à l'exemple couramment donné, cela fonctionne comme suit:
function *squareGen(x) {
var i;
for (i = 0; i < x; i++) {
yield i*i;
}
}
var gen = squareGen(3);
console.log(gen.next().value); // prints 0
console.log(gen.next().value); // prints 1
console.log(gen.next().value); // prints 4
Mais il y a aussi un deuxième objectif du mot-clé yield. Il peut être utilisé pour envoyer des valeurs au générateur.
Pour clarifier, un petit exemple:
function *sendStuff() {
y = yield (0);
yield y*y;
}
var gen = sendStuff();
console.log(gen.next().value); // prints 0
console.log(gen.next(2).value); // prints 4
Cela fonctionne, comme la valeur 2
est affectée à y
, en l'envoyant au générateur, après qu'il s'est arrêté au premier rendement (qui est revenu 0
).
Cela nous permet de faire des trucs vraiment funky. (regarde coroutine)
Il est utilisé pour les générateurs d'itérateurs. Fondamentalement, il vous permet de créer une séquence (potentiellement infinie) en utilisant du code procédural. Voir la documentation de Mozilla .
yield
peut également être utilisé pour éliminer l'enfer de rappel, avec un framework coroutine.
function start(routine, data) {
result = routine.next(data);
if(!result.done) {
result.value(function(err, data) {
if(err) routine.throw(err); // continue next iteration of routine with an exception
else start(routine, data); // continue next iteration of routine normally
});
}
}
// with nodejs as 'node --harmony'
fs = require('fs');
function read(path) {
return function(callback) { fs.readFile(path, {encoding:'utf8'}, callback); };
}
function* routine() {
text = yield read('/path/to/some/file.txt');
console.log(text);
}
// with mdn javascript 1.7
http.get = function(url) {
return function(callback) {
// make xhr request object,
// use callback(null, resonseText) on status 200,
// or callback(responseText) on status 500
};
};
function* routine() {
text = yield http.get('/path/to/some/file.txt');
console.log(text);
}
// invoked as.., on both mdn and nodejs
start(routine());
Générateur de séquence de Fibonacci utilisant le mot-clé yield.
function* fibbonaci(){
var a = -1, b = 1, c;
while(1){
c = a + b;
a = b;
b = c;
yield c;
}
}
var fibonacciGenerator = fibbonaci();
fibonacciGenerator.next().value; // 0
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 2
Yeild
mot-clé dans la fonction javaScript le rend générateur,
qu'est-ce qu'un générateur en javaScript?
Un générateur est une fonction qui produit une séquence de résultats au lieu d'une seule valeur, c'est-à-dire que vous générez une série de valeurs
Ce qui signifie que les générateurs nous aident à travailler de manière asynchrone avec les itérateurs d'aide, Oh maintenant, quels sont les itérateurs de piratage? vraiment?
Les itérateurs sont des moyens par lesquels nous pouvons accéder aux éléments un par un
d'où l'itérateur nous aide-t-il à accéder à un élément à la fois? il nous aide à accéder aux éléments via les fonctions du générateur,
les fonctions génératrices sont celles dans lesquelles nous utilisons des yeild
mots clés, les mots clés yield nous aident à mettre en pause et à reprendre l'exécution de la fonction
voici un exemple rapide
function *getMeDrink() {
let question1 = yield 'soda or beer' // execution will pause here because of yield
if (question1 == 'soda') {
return 'here you get your soda'
}
if (question1 == 'beer') {
let question2 = yield 'Whats your age' // execution will pause here because of yield
if (question2 > 18) {
return "ok you are eligible for it"
} else {
return 'Shhhh!!!!'
}
}
}
let _getMeDrink = getMeDrink() // initialize it
_getMeDrink.next().value // "soda or beer"
_getMeDrink.next('beer').value // "Whats your age"
_getMeDrink.next('20').value // "ok you are eligible for it"
_getMeDrink.next().value // undefined
permettez-moi d'expliquer brièvement ce qui se passe
vous avez remarqué que l'exécution est suspendue à chaque yeild
mot clé et nous pouvons y accéder en premier yield
avec l'aide de l'itérateur.next()
cela itère pour tous les yield
mots clés un par un, puis retourne indéfini lorsqu'il ne reste plus de yield
mots clés dans des mots simples, vous pouvez dire que le yield
mot clé est le point d'arrêt où la fonction s'arrête à chaque fois et ne reprend que lors de l'appel à l'aide de l'itérateur
pour notre cas: _getMeDrink.next()
c'est un exemple d'itérateur qui nous aide à accéder à chaque point d'arrêt en fonction
Exemple de générateurs:
async/await
si vous voyez la mise en œuvre de async/await
vous verrez generator functions & promises
sont utilisés pour faire du async/await
travail
veuillez signaler toute suggestion est la bienvenue
Dépendance entre les appels javascript asynchrones.
Un autre bon exemple de la façon dont le rendement peut être utilisé.
function request(url) {
axios.get(url).then((reponse) => {
it.next(response);
})
}
function* main() {
const result1 = yield request('http://some.api.com' );
const result2 = yield request('http://some.otherapi?id=' + result1.id );
console.log('Your response is: ' + result2.value);
}
var it = main();
it.next()
Avant d'en savoir plus sur le rendement, vous devez connaître les générateurs. Les générateurs sont créés à l'aide de la function*
syntaxe. Les fonctions de générateur n'exécutent pas de code mais renvoient à la place un type d'itérateur appelé générateur. Lorsqu'une valeur est donnée à l'aide de la next
méthode, la fonction générateur continue de s'exécuter jusqu'à ce qu'elle rencontre un mot clé yield. Utiliser yield
vous redonne un objet contenant deux valeurs, l'une est valeur et l'autre est fait (booléen). La valeur peut être un tableau, un objet, etc.
Un exemple simple:
const strArr = ["red", "green", "blue", "black"];
const strGen = function*() {
for(let str of strArr) {
yield str;
}
};
let gen = strGen();
for (let i = 0; i < 5; i++) {
console.log(gen.next())
}
//prints: {value: "red", done: false} -> 5 times with different colors, if you try it again as below:
console.log(gen.next());
//prints: {value: undefined, done: true}
J'essaie également de comprendre le mot clé yield. D'après ma compréhension actuelle, dans le générateur, le mot clé yield fonctionne comme un changement de contexte CPU. Lorsque l'instruction yield est exécutée, tous les états (par exemple, les variables locales) sont enregistrés.
En plus de cela, un objet de résultat direct sera retourné à l'appelant, comme {valeur: 0, done: false}. L'appelant peut utiliser cet objet de résultat pour décider de «réveiller» à nouveau le générateur en appelant next () (appeler next () revient à itérer l'exécution).
Une autre chose importante est qu'il peut définir une valeur sur une variable locale. Cette valeur peut être transmise par l'appelant «next ()» lors du «réveil» du générateur. par exemple, it.next ('valueToPass'), comme ceci: "resultValue = yield slowQuery (1);" Tout comme lors du réveil d'une prochaine exécution, l'appelant peut injecter un résultat en cours d'exécution (en l'injectant dans une variable locale). Ainsi, pour cette exécution, il existe deux types d'état:
le contexte enregistré lors de la dernière exécution.
Les valeurs injectées par le déclencheur de cette exécution.
Ainsi, avec cette fonctionnalité, le générateur peut trier plusieurs opérations asynchrones. Le résultat de la première requête asynchrone sera transmis à la seconde en définissant la variable locale (resultValue dans l'exemple ci-dessus). La deuxième requête asynchrone ne peut être déclenchée que par la réponse de la première requête asynchrone. Ensuite, la deuxième requête asynchrone peut vérifier la valeur de la variable locale pour décider des étapes suivantes car la variable locale est une valeur injectée à partir de la réponse de la première requête.
Les difficultés des requêtes asynchrones sont:
enfer de rappel
perdre du contexte à moins de les passer comme paramètres dans le rappel.
le rendement et le générateur peuvent aider les deux.
Sans rendement et générateur, pour trier plusieurs requêtes asynchrones nécessite un rappel imbriqué avec des paramètres comme contexte qui n'est pas facile à lire et à maintenir.
Voici un exemple de requêtes asynchrones chaînées qui s'exécutent avec nodejs:
const axios = require('axios');
function slowQuery(url) {
axios.get(url)
.then(function (response) {
it.next(1);
})
.catch(function (error) {
it.next(0);
})
}
function* myGen(i=0) {
let queryResult = 0;
console.log("query1", queryResult);
queryResult = yield slowQuery('https://google.com');
if(queryResult == 1) {
console.log("query2", queryResult);
//change it to the correct url and run again.
queryResult = yield slowQuery('https://1111111111google.com');
}
if(queryResult == 1) {
console.log("query3", queryResult);
queryResult = yield slowQuery('https://google.com');
} else {
console.log("query4", queryResult);
queryResult = yield slowQuery('https://google.com');
}
}
console.log("+++++++++++start+++++++++++");
let it = myGen();
let result = it.next();
console.log("+++++++++++end+++++++++++");
Ci-dessous le résultat en cours:
+++++++++++ start ++++++++++++
query1 0
+++++++++++ end ++++++++++++
query2 1
query4 0
Le modèle d'état ci-dessous peut faire la même chose pour l'exemple ci-dessus:
const axios = require('axios');
function slowQuery(url) {
axios.get(url)
.then(function (response) {
sm.next(1);
})
.catch(function (error) {
sm.next(0);
})
}
class StateMachine {
constructor () {
this.handler = handlerA;
this.next = (result = 1) => this.handler(this, result);
}
}
const handlerA = (sm, result) => {
const queryResult = result; //similar with generator injection
console.log("query1", queryResult);
slowQuery('https://google.com');
sm.handler = handlerB; //similar with yield;
};
const handlerB = (sm, result) => {
const queryResult = result; //similar with generator injection
if(queryResult == 1) {
console.log("query2", queryResult);
slowQuery('https://1111111111google.com');
}
sm.handler = handlerC; //similar with yield;
};
const handlerC = (sm, result) => {
const queryResult = result; //similar with generator injection;
if (result == 1 ) {
console.log("query3", queryResult);
slowQuery('https://google.com');
} else {
console.log("query4", queryResult);
slowQuery('https://google.com');
}
sm.handler = handlerEnd; //similar with yield;
};
const handlerEnd = (sm, result) => {};
console.log("+++++++++++start+++++++++++");
const sm = new StateMachine();
sm.next();
console.log("+++++++++++end+++++++++++");
Voici le résultat en cours:
+++++++++++ start ++++++++++++
query1 0
+++++++++++ end ++++++++++++
query2 1
query4 0
n'oubliez pas la syntaxe très utile «x du générateur» pour parcourir le générateur. Pas besoin du tout d'utiliser la fonction next ().
function* square(x){
for(i=0;i<100;i++){
x = x * 2;
yield x;
}
}
var gen = square(2);
for(x of gen){
console.log(x);
}