Le comportement des générateurs décrit dans l' article précédent n'est pas complexe, mais il est définitivement surprenant et peut sembler déroutant au premier abord. Donc, au lieu d'apprendre de nouveaux concepts, nous allons maintenant faire une pause et examiner un exemple intéressant d'utilisation de générateurs.
Ayons une fonction comme celle-ci:
function maybeAddNumbers() {
const a = maybeGetNumberA();
const b = maybeGetNumberB();
return a + b;
}
Fonctions
maybeGetNumberA
et
maybeGetNumberB
renvoyer des numéros, mais parfois ils peuvent renvoyer
null
ou
undefined
. Ceci est attesté par le mot «peut-être» dans leurs noms. Si cela se produit, n'essayez pas de mettre ces valeurs (par exemple, le nombre et
null
), il vaut mieux arrêter et revenir, disons
null
. A savoir
null
, et non une valeur imprévisible obtenue en ajoutant
null
/
undefined
avec un nombre ou autre
null
/
undefined
.
Vous devez donc vérifier que les nombres sont bien définis:
function maybeAddNumbers() {
const a = maybeGetNumberA();
const b = maybeGetNumberB();
if (a === null || a === undefined || b === null || b === undefined) {
return null;
}
return a + b;
}
Tout fonctionne, mais si elle
a
est
null
ou
undefined
, alors il ne sert à rien d' appeler la fonction
maybeGetNumberB
. Nous savons qu'il sera retourné de toute façon
null
.
Réécrivons la fonction:
function maybeAddNumbers() {
const a = maybeGetNumberA();
if (a === null || a === undefined) {
return null;
}
const b = maybeGetNumberB();
if (b === null || b === undefined) {
return null;
}
return a + b;
}
Donc. Au lieu de trois simples lignes de code, nous l'avons rapidement gonflée à 10 lignes (sans compter les vides). Et les fonctions sont maintenant appliquées
if
, que vous devez parcourir pour comprendre ce que fait la fonction. Et ce n'est qu'un exemple pédagogique! Imaginez une véritable base de code avec une logique beaucoup plus complexe, rendant ces vérifications encore plus difficiles. J'aimerais utiliser des générateurs ici et simplifier le code.
Regarde:
function* maybeAddNumbers() {
const a = yield maybeGetNumberA();
const b = yield maybeGetNumberB();
return a + b;
}
Et si nous pouvions laisser l'expression
yield <smething>
tester s'il s'agit d'une
<smething>
valeur réelle, et non
null
ou
undefined
? S'il s'avère que ce n'est pas un nombre, nous nous arrêtons et revenons simplement
null
, comme dans la version précédente du code.
C'est, vous pouvez écrire du code qui ressemble comme il fonctionne uniquement avec des valeurs réelles, définies. Le générateur peut vérifier cela et prendre les mesures appropriées pour vous! De la magie, non? Et ce n'est pas seulement possible, c'est facile à écrire!
Bien entendu, les générateurs eux-mêmes ne disposent pas de cette fonctionnalité. Ils renvoient simplement des itérateurs et vous pouvez réinsérer des valeurs dans les générateurs si vous le souhaitez. Nous devons donc écrire un wrapper, qu'il en soit ainsi
runMaybe
.
Au lieu d'appeler directement la fonction:
const result = maybeAddNumbers();
nous l'appellerons comme un argument wrapper:
const result = runMaybe(maybeAddNumbers());
Ce modèle est très courant dans les générateurs. À eux seuls, ils ne savent pas grand-chose, mais avec l'aide de wrappers auto-écrits, vous pouvez donner aux générateurs le comportement souhaité! C'est ce dont nous avons besoin maintenant.
runMaybe
- une fonction qui prend un argument: un itérateur créé par le générateur:
function runMaybe(iterator) {
}
Exécutons cet itérateur en boucle
while
. Pour ce faire, vous devez appeler l'itérateur pour la première fois et commencer à vérifier sa propriété
done
:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
}
}
À l'intérieur de la boucle, nous avons deux possibilités. Si
result.value
est
null
ou
undefined
, alors l'itération doit être arrêtée immédiatement et renvoyée
null
. Faisons cela:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
if (result.value === null || result.value === undefined) {
return null;
}
}
}
Ici, nous
return
arrêtons immédiatement l'itération avec de l'aide et revenons du wrapper
null
. Mais s'il
result.value
s'agit d'un nombre, alors vous devez "retourner" au générateur. Par exemple, si la
yield maybeGetNumberA()
fonction
maybeGetNumberA()
est un nombre, vous devez remplacer la
yield maybeGetNumberA()
valeur de ce nombre. Laissez-moi vous expliquer: disons que le résultat du calcul
maybeGetNumberA()
est de 5, puis nous le remplaçons
const a = yield maybeGetNumberA();
par
const a = 5;
. Comme vous pouvez le voir, nous n'avons pas besoin de changer la valeur extraite, il suffit de la renvoyer au générateur.
Nous nous souvenons que vous pouvez remplacer
yield <smething>
par une valeur en la passant comme argument à la méthode
next
dans un itérateur:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
if (result.value === null || result.value === undefined) {
return null;
}
// we are passing result.value back
// to the generator
result = iterator.next(result.value)
}
}
Comme vous pouvez le voir, le nouveau résultat est à nouveau stocké dans une variable
result
. Cela est possible car nous avons spécifiquement déclaré
result
utiliser
let
.
Maintenant, si le générateur rencontre un
null
/ lors de la récupération d'une valeur
undefined
, nous revenons simplement
null
du wrapper
runMaybe
.
Il reste à ajouter autre chose pour que le processus d'itération se termine sans détecter
null
/
undefined
. Après tout, si nous obtenons deux nombres, nous devons renvoyer leur somme depuis le wrapper!
Le générateur
maybeAddNumbers
se termine par une expression
return
. Nous comprenons que la présence
return <smething>
dans le générateur, il renvoie
next
un objet de l'appel
{ value: <smething>, done: true }
. Lorsque cela se produit, la boucle
while
s'arrête car la propriété
done
obtient une valeur
true
. Mais la dernière valeur retournée (dans notre cas particulier, ceci
a + b
) sera toujours stockée dans la propriété
result.value
! Et nous pouvons simplement le retourner:
function runMaybe(iterator) {
let result = iterator.next();
while(!result.done) {
if (result.value === null || result.value === undefined) {
return null;
}
result = iterator.next(result.value)
}
// just return the last value
// after the iterator is done
return result.value;
}
Et c'est tout!
Créons des fonctions
maybeGetNumberA
et
maybeGetNumberB
, et laissons-les retourner des nombres réels en premier:
const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => 10;
Exécutons le code et enregistrons le résultat:
function* maybeAddNumbers() {
const a = yield maybeGetNumberA();
const b = yield maybeGetNumberB();
return a + b;
}
const result = runMaybe(maybeAddNumbers());
console.log(result);
Comme prévu, le nombre 15 apparaîtra dans la console.
Maintenant, remplacez l'un des termes par
null
:
const maybeGetNumberA = () => null;
const maybeGetNumberB = () => 10;
Lors de l'exécution du code, nous obtenons
null
!
Cependant, il est important pour nous de nous assurer que la fonction n'est
maybeGetNumberB
pas appelée si elle
maybeGetNumberA
renvoie
null
/
undefined
. Vérifions à nouveau que le calcul a réussi. Pour ce faire, ajoutez simplement à la deuxième fonction
console.log
:
const maybeGetNumberA = () => null;
const maybeGetNumberB = () => {
console.log('B');
return 10;
}
Si nous avons écrit correctement le wrapper
runMaybe
, lorsque ce code est exécuté, la lettre n'apparaîtra
B
pas dans la console.
En effet, lors de l'exécution du code, nous verrons simplement
null
. Cela signifie que le wrapper arrête réellement le générateur dès qu'il détecte
null
/
undefined
.
Le code fonctionne comme prévu: il produit
null
n'importe quelle combinaison:
const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => 10;
const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => null;
const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => null;
Etc.
Mais l'avantage de cet exemple ne réside pas dans l'exécution de ce code particulier. Cela réside dans le fait que nous avons créé un wrapper universel qui peut fonctionner avec n'importe quel générateur capable d'extraire des valeurs
null
/
undefined
.
Écrivons une fonction plus complexe:
function* maybeAddFiveNumbers() {
const a = yield maybeGetNumberA();
const b = yield maybeGetNumberB();
const c = yield maybeGetNumberC();
const d = yield maybeGetNumberD();
const e = yield maybeGetNumberE();
return a + b + c + d + e;
}
Vous pouvez le faire dans notre emballage sans aucun problème
runMaybe
! En fait, cela n'a même pas d'importance pour le wrapper que nos fonctions renvoient des nombres. Après tout, nous n'y avons pas mentionné le type numérique. Ainsi, vous pouvez utiliser n'importe quelle valeur dans le générateur - nombres, chaînes, objets, tableaux, structures de données plus complexes - et cela fonctionnera avec notre wrapper!
C'est ce qui inspire les développeurs. Les générateurs vous permettent d'ajouter des fonctionnalités personnalisées à votre code, ce qui semble très courant (à part les appels, bien sûr
yield
). Il vous suffit de créer un wrapper qui itère le générateur d'une manière spéciale. Ainsi, le wrapper ajoute les fonctionnalités nécessaires au générateur, qui peut être n'importe quoi! Les générateurs ont des possibilités presque illimitées, tout dépend de notre imagination.