Quel est le mot clé yield en JavaScript?


238

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?



4
c'est expliqué dans MDN , mais je pense que cela ne fonctionne que pour Firefox, non? Est-il portable? Y a-t-il un moyen d'y parvenir sur Chrome ou node.js? PD: désolé, c'est Javascript v1.7 + , c'est donc la propriété à regarder lorsque vous cherchez du support.
Trylks

1
@Trylks: Les générateurs sont disponibles dans Node depuis la v0.11.2
Janus Troelsen

@JanusTroelsen cependant, seulement derrière un drapeau. Ils sont pris en charge nativement dans ioJS
Dan Pantry

Réponses:


86

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.


2
@NicolasBarbulesco il y a un exemple très évident si vous cliquez sur la documentation MDN.
Matt Ball

@MattBall - une fonction en javascript pour PI comme celle-ci serait-elle suffisante comme suit: fonction * PI {PI = ((Math.SQRT8;) / 9801;); } - ou y a-t-il déjà une fonction implémentée en javascript pour ce calcul de PI?
dschinn1001

4
Quel est l'intérêt de citer MDN ici? Je pense que tout le monde peut lire ça sur MDN. Visitez davidwalsh.name/promises pour en savoir plus à leur sujet.
Ejaz Karim

20
Comment cela a-t-il obtenu ~ 80 votes positifs quand (a) il s'agit d'une copie de la "documentation très médiocre" comme l'appelle l'interrogateur et (b) il ne dit rien d'utile? De bien meilleures réponses ci-dessous.
www-0av-Com du

4
si quelqu'un demande des explications, il suffit de copier-coller une documentation est totalement inutile. Demander signifie que vous avez déjà cherché dans les documents mais que vous ne les avez pas compris.
Diego

205

Réponse tardive, tout le monde est probablement au courant yieldmaintenant, 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 yieldpeu comme return: vous récupérez quelque chose. return xrenvoie la valeur de x, mais yield xrenvoie 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.


13
Utile, mais je suppose que vous function* foo(x){y êtes
Rana Deep

9
@RanaDeep: La syntaxe de la fonction est étendue pour ajouter un jeton facultatif * . 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 functionqu'avec yield.
évêque

1
@ Ajedi32 Oui, vous avez raison. Harmony a normalisé la corrélation entre function *et yieldet 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!
évêque

3
@MuhammadUmer Js est enfin devenu un langage que vous pouvez réellement utiliser. Cela s'appelle l'évolution.
Lukas Liesis

1
l'exemple est utile, mais ... qu'est-ce qu'une fonction *?
Diego

66

C'est vraiment simple, c'est comme ça que ça marche

  • yieldLe mot clé permet simplement de suspendre et de reprendre une fonction à tout moment de manière asynchrone .
  • De plus, cela aide à renvoyer la valeur d'une fonction de générateur .

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 

Essayez-le maintenant

Coutumes

  • Évaluation paresseuse
  • Séquences infinies
  • Flux de contrôle asynchrones

Références:


54

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 yieldest 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 loadFromDBappel) doit être à l'intérieur de ce rappel laid. C'est mauvais pour plusieurs raisons ...

  • Tout votre code est en retrait d'un niveau dans
  • Vous avez cette fin })que vous devez suivre partout
  • Tout ce function (err, result)jargon supplémentaire
  • Pas vraiment clair que vous faites cela pour attribuer une valeur à result

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 yields'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 yielden 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 !


a function*est juste une fonction régulière sans rendement?
Abdul

Je pense que vous voulez dire que function *c'est une fonction qui contient du rendement. C'est une fonction spéciale appelée générateur.
Leander

7
Pour les personnes qui utilisent déjà yieldpartout, 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.
palswim

cet article est difficile à comprendre
Martian2049

18

Pour donner une réponse complète: yieldfonctionne 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 2est 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)



6

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());

4

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 

4

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 yeildmots 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 yeildmot clé et nous pouvons y accéder en premier yieldavec l'aide de l'itérateur.next()

cela itère pour tous les yieldmots clés un par un, puis retourne indéfini lorsqu'il ne reste plus de yieldmots clés dans des mots simples, vous pouvez dire que le yieldmot 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 & promisessont utilisés pour faire du async/awaittravail

veuillez signaler toute suggestion est la bienvenue


3

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()


0

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 nextméthode, la fonction générateur continue de s'exécuter jusqu'à ce qu'elle rencontre un mot clé yield. Utiliser yieldvous 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.


0

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}

0

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:

  1. le contexte enregistré lors de la dernière exécution.

  2. 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:

  1. enfer de rappel

  2. 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


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);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.