Une fonction ordinaire va jusqu'au bout : elle démarre, calcule, rend un résultat, et son travail est terminé. Une fonction génératrice, elle, sait s'arrêter au milieu, rendre une valeur, et attendre qu'on lui demande la suite.
yield est le mot-clé qui marque ces points d'arrêt. C'est la seule construction du langage qui permette à une fonction de garder ses variables locales entre deux appels sans les stocker ailleurs.
Définition
yield rend une valeur au code appelant et met en pause la fonction génératrice qui le contient, dont l'exécution reprendra exactement à cette ligne au prochain appel de next().
Une fonction génératrice se déclare avec une étoile : function* nom(). L'appeler n'exécute rien, cela crée un itérateur, un objet muni d'une méthode next().
function* compteur() {
yield 1;
yield 2;
return 'fini';
}
const it = compteur();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 'fini', done: true }
it.next(); // { value: undefined, done: true }Chaque appel renvoie un objet à deux clés : la valeur rendue, et un drapeau qui indique si la fonction est arrivée au bout.
Ce que yield n'est pas
| Mot-clé | Effet sur la fonction | Reprise possible |
|---|---|---|
| return | Termine définitivement | Non |
yield | Met en pause | Oui, au prochain next() |
| await | Met en pause jusqu'à une promesse | Automatique |
À noter : la valeur passée à return ferme la série mais n'en fait pas partie. Un Spread (...) sur ce générateur donne [1, 2], sans le 'fini'.
Suites infinies et délégation
Comme rien n'est calculé avant qu'on le demande, une fonction génératrice peut décrire une suite sans fin sans jamais saturer la mémoire.
function* identifiants() {
let n = 1;
while (true) yield n++;
}
const ids = identifiants();
ids.next().value; // 1
ids.next().value; // 2L'étoile sert aussi à déléguer : yield* passe la main à un autre générateur, ou à n'importe quelle valeur parcourable, et rend toutes ses valeurs avant de continuer.
function* interieur() {
yield 'a';
yield 'b';
}
function* exterieur() {
yield 'debut';
yield* interieur();
yield 'fin';
}
[...exterieur()]; // ['debut', 'a', 'b', 'fin']La communication va dans les deux sens. La valeur passée à next() devient le résultat du yield en attente, ce qui permet d'écrire une fonction qui pose une question, se met en pause, et reprend avec la réponse.
Questions fréquentes
À quoi cela sert-il concrètement ?
Aux séquences dont on ne veut pas calculer tous les éléments d'avance : pagination d'une API, lecture d'un très gros fichier morceau par morceau, identifiants uniques. Un générateur produit ce qu'on lui demande, quand on le lui demande, là où un Array (tableau) exigerait de tout construire d'abord.
Peut-on écrire yield hors d'une fonction génératrice ?
Non, le fichier ne se charge pas : le mot-clé n'a de sens que dans une fonction déclarée avec une étoile. La même erreur arrive souvent en le plaçant dans une fonction fléchée imbriquée, qui ne peut pas être génératrice.
Faut-il maîtriser les générateurs pour travailler en JavaScript ?
On en écrit rarement à la main, mais on en croise : les itérateurs asynchrones, parcourus avec for await, reposent dessus, et certaines bibliothèques de gestion d'état les utilisent pour décrire des enchaînements. Savoir lire un function* suffit largement avant d'avoir à en écrire un.