Le guide JavaScript avancé: générateurs. Partie 2, un cas d'utilisation simple



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.



All Articles