Rajesh Babu

Follow

4 mai, 2018 – 9 min lu

.

Si vous êtes un développeur JavaScript depuis deux à cinq ans, vous avez certainement rencontré des posts parlant de Générateurs et d’Itérateurs. Bien que les générateurs et les itérateurs soient intrinsèquement associés, les générateurs semblent un peu plus intimidants que l’autre.

Générateur Turbine

Les itérateurs sont une implémentation d’objets Iterables tels que les cartes, les tableaux et les chaînes de caractères qui nous permet d’itérer sur eux en utilisant next(). Ils ont une grande variété de cas d’utilisation à travers les générateurs, les observables et les opérateurs de propagation.

Je recommande le lien suivant pour ceux d’entre vous qui sont nouveaux aux itérateurs, Guide to Iterators.

Pour vérifier si votre objet est conforme au protocole itérable, vérifiez en utilisant le Symbol.iterator intégré:

Les générateurs introduits dans le cadre de ES6 n’ont pas subi de changements pour les versions ultérieures de JavaScript et ils sont là pour rester plus longtemps. Je veux dire vraiment longtemps ! Il n’y a donc pas moyen de s’y soustraire. Bien que ES7 et ES8 aient quelques nouvelles mises à jour, elles n’ont pas la même ampleur de changement que ES6 par rapport à ES5, qui a fait passer JavaScript au niveau suivant, pour ainsi dire.

À la fin de ce post, je suis positif que vous aurez une solide compréhension du fonctionnement des Générateurs de fonctions. Si vous êtes un pro, aidez-moi à améliorer le contenu en ajoutant vos commentaires dans les réponses. Juste au cas où vous auriez des difficultés à suivre le code, j’ai également ajouté des explications pour la plupart du code pour vous aider à mieux comprendre.

Les fonctions en JavaScript, comme nous le savons tous, « s’exécutent jusqu’au retour/fin ». Les fonctions génératrices, quant à elles,  » s’exécutent jusqu’à ce que le rendement/le retour/la fin « . Contrairement aux fonctions normales les Generator Functions une fois appelées, renvoient l’objet Generator, qui contient l’intégralité de l’Iterable Generator qui peut être itéré en utilisant la méthode next() ou for…of loop.

Chaque appel next() sur le générateur exécute chaque ligne de code jusqu’au prochain yield qu’il rencontre et suspend temporairement son exécution.

Syntaxiquement, ils sont identifiés par un *, soit fonction* X ou fonction *X, – les deux signifient la même chose.

Une fois créé, l’appel de la fonction générateur renvoie l’objet générateur. Cet objet générateur doit être affecté à une variable pour garder la trace des méthodes next() ultérieures appelées sur lui-même. Si le générateur n’est pas assigné à une variable, alors il ne donnera toujours que jusqu’à la première expression yield sur chaque next().

Les fonctions de générateur sont normalement construites en utilisant des expressions yield. Chaque yield à l’intérieur de la fonction de générateur est un point d’arrêt avant le début du cycle d’exécution suivant. Chaque cycle d’exécution est déclenché au moyen de la méthode next() sur le générateur.

À chaque appel next(), l’expression yield renvoie sa valeur sous la forme d’un objet contenant les paramètres suivants.

{ value: 10, done: false } // assuming that 10 is the value of yield

  • Valeur – est tout ce qui est écrit sur le côté droit du mot clé yield, il peut s’agir d’un appel de fonction, d’un objet ou pratiquement de n’importe quoi. Pour les yields vides, cette valeur est indéfinie.
  • Done – indique l’état du générateur, s’il peut être exécuté plus loin ou non. Lorsque done renvoie true, cela signifie que la fonction a terminé son exécution.

(Si vous sentez que cela vous dépasse un peu, vous aurez plus de clarté une fois que vous aurez vu l’exemple ci-dessous…)

Fonction de générateur de base

Note : Dans l’exemple ci-dessus, la fonction générateur à laquelle on accède directement sans wrapper ne s’exécute toujours que jusqu’au premier yield. Par conséquent, par définition, vous devez affecter le générateur à une variable pour itérer correctement sur lui.

Cycle de vie d’une fonction génératrice

Avant de poursuivre, jetons un coup d’œil rapide au schéma fonctionnel du cycle de vie de la fonction génératrice :

Cycle de vie d’une fonction génératrice

Chaque fois qu’un rendement est rencontré, la fonction génératrice retourne un objet contenant la valeur du rendement rencontré et l’état effectué. De même, lorsqu’un retour est rencontré, nous obtenons la valeur du retour et aussi le statut done comme vrai. Chaque fois que, le statut done est retourné comme vrai, cela signifie essentiellement que la fonction générateur a terminé son exécution, et qu’aucun autre yield n’est possible.

Tout ce qui suit le premier return est ignoré, y compris les autres expressions yield.

Lisez la suite pour mieux comprendre le schéma fonctionnel.

Assigner le yield à une variable

Dans l’échantillon de code précédent, nous avons vu une introduction à la création d’un générateur de base avec un yield. Et obtenu la sortie attendue. Maintenant, supposons que nous assignons l’expression yield entière à une variable dans le code ci-dessous.

Assignation du yield à une variable

Quel est le résultat de l’expression yield entière passée à la variable ? Rien ou indéfini …

Pourquoi ? À partir de la seconde next(), le yield précédent est remplacé par les arguments passés dans la fonction suivante. Puisque, nous ne passons rien ici dans la méthode next, son supposé que l’ensemble de la ‘previous-yield expression’ comme indéfini.

Avec cela à l’esprit, sautons à la section suivante pour comprendre plus sur le passage d’argument à la méthode next().

Passing Arguments to the next() Method

Avec la référence au diagramme de bloc ci-dessus, parlons du passage d’arguments dans la fonction next. C’est l’une des parties les plus délicates de toute l’implémentation du générateur.

Considérons le morceau de code suivant, où le yield est assigné à une variable, mais cette fois nous passons une valeur dans la méthode next().

Regardons le code ci-dessous dans la console. Et l’explication juste après.

Passage des arguments au next()

Explication:

  1. Lorsque nous appelons le premier next(20), chaque ligne de code jusqu’au premier yield est imprimée. Comme nous n’avons pas d’expression de rendement précédente, cette valeur 20 est rejetée. Dans la sortie, nous obtenons la valeur de rendement comme i*10, qui est ici 100. De plus, l’état de l’exécution s’arrête avec le premier yield et la const j n’est pas encore fixée.
  2. Le deuxième appel next(10), remplace toute la première expression yield par 10, imaginez yield (i * 10) = 10, qui continue à fixer la valeur de la const j à 50 avant de retourner la valeur du second yield. La valeur du rendement est ici 2 * 50 / 4 = 25.
  3. Troisième next(5), remplace l’ensemble du deuxième rendement par 5, portant la valeur de k à 5. Et plus loin, continue à exécuter l’instruction return et renvoie (x + y + z) => (10 + 50 + 5) = 65 comme valeur de rendement final avec done true.

Cela pourrait être un peu écrasant pour les lecteurs de la première heure, mais prenez 5 bonnes minutes pour le lire encore et encore pour le comprendre complètement.

Passer le yield comme un argument d’une fonction

Il y a n-nombre de cas d’utilisation autour du yield concernant la façon dont il peut être utilisé à l’intérieur d’un générateur de fonctions. Regardons le code ci-dessous pour une telle utilisation intéressante de yield, ainsi que l’explication.

Yield comme argument d’une fonction

Explication

  1. Le premier next() donne une valeur indéfinie car l’expression yield n’a pas de valeur.
  2. Le deuxième next() donne « je suis inutile », la valeur qui a été passée. Et prépare l’argument pour l’appel de la fonction.
  3. Le troisième next(), appelle la fonction avec un argument indéfini. Comme mentionné ci-dessus, la méthode next() appelée sans aucun argument signifie essentiellement que toute l’expression yield précédente est indéfinie. Par conséquent, cela imprime undefined et termine l’exécution.

Yield avec un appel de fonction

En dehors de retourner des valeurs yield peut également appeler des fonctions et retourner la valeur ou imprimer la même. Regardons le code ci-dessous et comprenons mieux.

Yield appelant une fonction

Le code ci-dessus renvoie le retour obj de la fonction comme valeur yield. Et termine l’exécution en mettant undefined au const user.

Yield avec des promesses

Yield avec des promesses suit la même approche que l’appel de fonction ci-dessus, au lieu de retourner une valeur de la fonction, il retourne une promesse qui peut être évaluée plus loin pour le succès ou l’échec. Regardons le code ci-dessous pour comprendre comment cela fonctionne.

Yield with promises

The apiCall returns the promises as the yield value, when resolved after 2 seconds prints the value we need.

Yield*

Jusqu’ici nous avons examiné les cas d’utilisation de l’expression yield, maintenant nous allons examiner une autre expression appelée yield*. Yield* lorsqu’il est utilisé à l’intérieur d’une fonction de générateur délègue une autre fonction de générateur. En termes simples, il complète de manière synchrone la fonction de générateur dans son expression avant de passer à la ligne suivante.

Regardons le code et l’explication ci-dessous pour mieux comprendre. Ce code est tiré de la docs web MDN.

Basic yield*

Explication

  1. Le premier appel next() donne une valeur de 1.
  2. Le deuxième appel next(), cependant, est une expression yield*, ce qui signifie intrinsèquement que nous allons compléter une autre fonction de générateur spécifiée dans l’expression yield* avant de continuer la fonction de générateur actuelle.
  3. Dans votre esprit, vous pouvez supposer que le code ci-dessus est remplacé comme celui ci-dessous
function* g2() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}

Ce dernier va continuer pour terminer l’exécution du générateur. Cependant, il y a une capacité distincte du yield* que vous devriez garder à l’esprit en utilisant return, dans la section suivante.

Yield* avec retour

Le yield* avec retour se comporte un peu différemment du yield* normal. Lorsque yield* est utilisé avec une déclaration de retour, il s’évalue à cette valeur, ce qui signifie que la fonction yield* entière() devient égale à la valeur renvoyée par la fonction génératrice associée.

Regardons le code et l’explication ci-dessous pour mieux le comprendre.

Rendement* avec return

Explication

  1. Dans le premier next(), nous allons directement au yield 1 et retournons sa valeur.
  2. Le deuxième next() donne le rendement 2.
  3. Le troisième next(), renvoie ‘foo’ et continue à donner le rendement ‘la fin’, assignant ‘foo’ au résultat const au passage.
  4. Le dernier next() termine l’exécution.

Yield* avec un objet itérable intégré

Il y a une autre propriété intéressante de yield* qui mérite d’être mentionnée, similaire à la valeur de retour, yield* peut également itérer sur des objets itérables comme Array, String et Map.

Regardons comment cela fonctionne en temps réel.

Yield sur des itérables intégrés

Ici, dans le code, le yield* itère sur chaque objet itérable possible qui est passé comme son expression. Je suppose que le code lui-même est auto-explicatif.

Best Practices

En plus de tout cela, chaque itérateur/générateur peut être itéré sur une boucle for…of. Semblable à notre méthode next() qui est appelée explicitement, la boucle for…of passe en interne à l’itération suivante en fonction du mot-clé yield. Et elle itère seulement jusqu’au dernier yield et ne traite pas les déclarations de retour comme la méthode next().

Vous pouvez vérifier la même chose dans le code ci-dessous.

Yield with for…of

La valeur de retour finale n’est pas imprimée, car la boucle for…of itère seulement jusqu’au dernier yield. Ainsi, il relève de la meilleure pratique d’éviter les déclarations de retour à l’intérieur d’une fonction de générateur, car cela affecterait la réutilisabilité de la fonction lorsqu’elle est itérée sur un for…of.

Conclusion

J’espère que cela couvre les cas d’utilisation de base des fonctions de générateur et j’espère sincèrement que cela a donné une meilleure compréhension du fonctionnement des générateurs en JavaScript ES6 et plus. Si vous aimez mon contenu s’il vous plaît laissez 1, 2, 3 ou même 50 claps :).

Veuillez me suivre sur mon compte GitHub pour plus de projets JavaScript et Full-Stack:

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.