Node.js Generators et comparer avec les rappels
⚡ Résumé intelligent
GeneratorDans Node.js, les fonctions `yield` sont des fonctions dont l'exécution peut être suspendue et reprise à la demande. Le mot-clé `yield` met la fonction en pause, la méthode `next` la reprend, et cette association permet de simplifier des fonctions de rappel profondément imbriquées en un code séquentiel lisible.
Dans cette présentation, nous allons examiner les générateurs et en quoi ils diffèrent des fonctions de rappel.
Que sont les générateurs ?
Generators est devenu populaire en Node.js en raison de ce dont ils sont capables.
- GeneratorLes s sont des exécutions de fonctions qui peuvent être suspendues et reprises ultérieurement.
- GeneratorLes fonctions de suspension sont utiles pour des concepts tels que « l'exécution paresseuse ». En suspendant l'exécution et en la reprenant à volonté, les valeurs ne sont extraites que lorsqu'elles sont nécessaires.
Generators ont les deux méthodes principales ci-dessous.
- Méthode de rendement — appelée à l'intérieur d'une fonction pour interrompre l'exécution à la ligne exacte où
yieldapparaît. - Méthode suivante — appelée depuis l'application principale pour reprendre une fonction qui a cédé la main. L'exécution se poursuit jusqu'à la prochaine
yield, ou jusqu'à la fin de la fonction.
Prenons l'exemple d'une fonction d'addition simple qui additionne deux nombres, en interrompant l'exécution à différents moments pour illustrer le comportement des générateurs.
function* Add(x) { yield x + 1; // The value passed into the second next() call lands here var y = yield(null); return x + y; } var gen = Add(5); gen.next(); // runs to the first yield, returns { value: 6, done: false } gen.next(6); // resumes, y becomes 6, returns { value: 11, done: true }
Code Explication
- La première étape consiste à définir la fonction génératrice. Notez le
*ajoutée à lafunctionmot-clé. La fonction est appeléeAddet prend un paramètrex. - Le
yieldCe mot-clé est spécifique aux générateurs et interrompt l'exécution de la fonction. L'exécution s'arrête ici jusqu'à ce que…next()est invoquée. - La deuxième
yieldLa fonction est à la fois mise en pause et reçoit une valeur : celle qui est passée à la fonction suivante.next()l'appel devient la valeur dey. - appel
gen.next()La première exécution rattrape la fonction jusqu'au premieryield. Appelgen.next(6)reprend le projet, attribue 6 ày, et retournex + y.
⚠️ Correction : la liste originale contenait la ligne y = 6 juste après var y = yield(null);Cette affectation écrase toute valeur existante. next() fournitures, ce qui contredit l'objectif de l'exemple et rend le second yield Sans signification. La valeur est maintenant transmise par gen.next(6), c'est ainsi qu'un générateur reçoit réellement des données de l'appelant.
Rappels vs générateurs
Generators résout le problème connu sous le nom de « callback hell ». Les fonctions de rappel peuvent parfois devenir si profondément imbriquées lors du développement d'une application Node.js qu'elles sont difficiles à suivre.
C’est là que les générateurs s’avèrent utiles. Un exemple courant est la création de fonctions de temporisation. L’exemple ci-dessous construit une fonction de temporisation simple, puis l’appelle avec des délais de 1000, 2000 et 3000 ms.
Étape 1) Définissez la fonction de rappel avec le code de délai nécessaire.
function Timedelay(ptime, callback) { setTimeout(function() { callback("Pausing for " + ptime); }, ptime); // the delay must use ptime, the parameter name }
Code Explication
- Une fonction appelée
Timedelayest créé avec un paramètreptime, qui indique le nombre de millisecondes de pause. setTimeoutattend ce nombre de millisecondes, puis invoque la fonction de rappel fournie.
⚠️ Correction : Le code original a été validé. time comme deuxième argument à setTimeout, mais aucune variable nommée time existe — le paramètre est ptimeTel qu'il est écrit, il lance ReferenceError: time is not defined.
Étape 2) Considérons maintenant le code utilisant des fonctions de rappel. Pour chaîner des délais de 1000, 2000 et 3000 millisecondes, les fonctions de rappel sont imbriquées les unes dans les autres.
Timedelay(1000, function(message) { console.log(message); Timedelay(2000, function(message) { console.log(message); Timedelay(3000, function(message) { console.log(message); }); }); });
Code Explication
Timedelayest appelée en tant que fonction de rappel avec la valeur 1000.- À l'intérieur de cette fonction de rappel,
Timedelayest rappelé avec 2000. - À l'intérieur de celui-là,
Timedelayest appelé une troisième fois avec 3000.
⚠️ Correction : chaque rappel dans le journal d'origine msg, mais le paramètre est nommé messageLes trois lignes ont lancé ReferenceError: msg is not defined.
Comme vous pouvez le constater dans le code ci-dessus, la situation se complique à chaque appel supplémentaire — cette imbrication est ce que décrit le « callback hell ».
Étape 3) Voyez maintenant le même comportement exprimé avec des générateurs. La séquence se lit comme trois lignes plates plutôt que comme trois niveaux d'imbrication.
function* Messages() { console.log(yield Timedelay(1000)); console.log(yield Timedelay(2000)); console.log(yield Timedelay(3000)); }
Code Explication
- Une fonction génératrice est définie pour appeler la
Timedelayla fonction. yields'utilise avecTimedelayet 1000 comme valeur du paramètre.yieldest utilisé à nouveau avec 2000, et une fois de plus avec 3000.
⚠️ Correction : l'original a écrit console,log(...) avec une virgule au lieu d'un point sur les trois lignes, ce qui constitue une erreur de syntaxe et empêche l'analyse du fichier.
Comment exécuter un Generator avec une fonction Runner
Le générateur ci-dessus est très clair, mais il ne fonctionnera pas de lui-même. C'est un détail que la plupart des explications omettent, et c'est pourquoi la version simplifiée peut paraître magique. Un générateur est une machine en pause ; il faut donc que quelque chose appuie constamment sur le bouton. Messages() Cette fonction crée simplement l'objet générateur et retourne immédiatement ; aucun minuteur ne démarre et rien n'est affiché.
L'élément manquant est un exécuteur : une petite fonction qui appelle next(), attend que le travail asynchrone soit terminé, puis appelle next() et voici le résultat.
// Timedelay now hands its result back to the runner function Timedelay(ptime) { return function(resume) { setTimeout(function() { resume("Pausing for " + ptime); }, ptime); }; } // The runner drives the generator to completion function run(generatorFunction) { var it = generatorFunction(); function step(value) { var result = it.next(value); if (result.done) return; // result.value is the function returned by Timedelay result.value(step); } step(); } run(Messages);
Trois points permettent à cela de fonctionner. Premièrement, Timedelay Elle ne prend plus directement de fonction de rappel ; elle renvoie une fonction que l’exécuteur peut invoquer, lui rendant le contrôle lorsque le minuteur se déclenche. Deuxièmement, it.next(value) réinjecte le résultat précédent dans le générateur en pause, ce qui permet yield figurer à droite d'une tâche. Troisièmement, le coureur s'arrête dès que result.done La condition devient vraie, la récursion s'arrête donc naturellement. Ce modèle de fonctionnement est précisément ce que proposent des bibliothèques telles que co, et c'est le mécanisme qui async/await plus tard, absorbée par la langue elle-même.
Generators vs async/await : lequel utiliser ?
GeneratorLes s étaient la méthode standard pour écrire du code asynchrone plat avant async/await Les deux existent et partagent la même idée sous-jacente : interrompre l’exécution d’une fonction en cours. Node prend en charge cette fonctionnalité. async/await nativement depuis la version 7.6, ce qui modifie la recommandation pour toute personne commençant un nouveau travail aujourd'hui.
| Aspect | Generators | asynchroniser / attendre |
|---|---|---|
| Coursier recherché | Oui, écrit par vous ou par une bibliothèque | Non, c'est inhérent à la langue. |
| Mot-clé de pause | yield |
await |
| La gestion des erreurs | Le coureur doit transmettre les erreurs | Essai ordinaire et attraper |
| Peut s'interrompre en cours de route | Oui, et la reprise peut être sélective. | Non, il court jusqu'à la fin. |
| Meilleur pour | Séquences paresseuses et flux infinis | Travail asynchrone ordinaire |
Utilisez le async/await Pour un flux asynchrone dans le nouveau code, il est préférable de supprimer le gestionnaire d'événements et de rétablir le mécanisme classique try/catch. Réservez les générateurs à leurs seules applications : production différée de valeurs, itération sur de longues séquences ou des séquences infinies, et tout cas où le consommateur détermine précisément le moment du calcul de la valeur suivante.
Commun Generator Erreurs et comment les corriger
La plupart des problèmes de générateurs proviennent de l'oubli que la fonction est inactive jusqu'à ce qu'un appel soit effectué. next()ou de petites erreurs de syntaxe autour de l'astérisque. Chaque symptôme ci-dessous indique sa cause et sa solution.
- Rien ne se passe lorsque le générateur est appelé : L'appel renvoie un itérateur, et non un résultat. Appeler
next(), ou passez-le à un coureur. - Erreur de syntaxe lors de l'exécution de yield : L'astérisque est manquant.
yieldn'est valable qu'à l'intérieur d'unfunction*. - La valeur renvoyée par next() est indéfinie : le premier
next()L'appel ne peut pas fournir de valeur, car le générateur n'a pas atteint de valeur.yieldPour l'instant. Envoyez les données à partir du deuxième appel. - Boucle infinie dans un jeu de course :
result.donen'est jamais vérifié, doncnext()L'appel se poursuit au-delà de la fin. La fonction « Toujours retourner lorsque terminé » est vraie. - Les erreurs disparaissent silencieusement : Un rejet survenant lors de l'étape asynchrone n'est jamais renvoyé au générateur. Transmettez-le avec
it.throw(err)afin qu'un bloc try-catch écrit à l'intérieur du générateur puisse effectivement le voir et le gérer.





