Sobes.tech
Middle+

Pourquoi les générateurs ordinaires permettent-ils d'écrire du code asynchrone?

sobes.tech IA

Réponse de l'IA

Les générateurs courants en JavaScript permettent d’écrire du code asynchrone grâce à leur capacité à suspendre et reprendre l’exécution, ainsi qu’à transmettre des valeurs dans les deux sens (via yield et next()).

Voici comment cela fonctionne dans le contexte de l’asynchronie :

  1. Suspension de l’exécution (yield) : Lorsqu’on atteint l’instruction yield, le générateur est suspendu et renvoie une valeur. En code asynchrone, cette valeur peut être une promesse ou une autre "action" à réaliser de manière asynchrone.

  2. Attente du résultat asynchrone : Le code externe (par exemple, un "runner" ou un wrapper pour le générateur) reçoit la valeur passée via yield. Si c’est une promesse, il attend sa résolution ou son rejet.

  3. Reprise de l’exécution (next()) : Après la résolution de l’opération asynchrone (résolution de la promesse), le résultat asynchrone (la valeur de la promesse) est renvoyé dans le générateur via la méthode next(). Le générateur reprend l’exécution là où il s’était arrêté.

  4. Transmission du résultat : La valeur passée à next() devient le résultat de l’expression yield à l’intérieur du générateur. Ainsi, le générateur peut "obtenir" le résultat de l’opération asynchrone et continuer à travailler avec, comme si le code était synchrone.

Ce pattern crée une illusion d’exécution synchrone pour des opérations asynchrones, rendant le code plus lisible et plus facile à gérer comparé à l’utilisation traditionnelle de callbacks ou de chaînes .then() complexes.

Un exemple de cela est l’utilisation de wrappers comme co (même si aujourd’hui, async/await est plus courant, qui sont essentiellement du sucre syntaxique sur les générateurs et les promesses) :

// Exemple d’utilisation d’un générateur pour l’asynchronie
function* myAsyncGenerator() {
  try {
    const result1 = yield Promise.resolve(1); // Suspension, retourne une promesse
    console.log('Premier résultat reçu :', result1); // Reprise avec la valeur

    const result2 = yield new Promise(resolve => setTimeout(() => resolve(2), 500)); // Une autre opération asynchrone
    console.log('Deuxième résultat reçu :', result2);

    return 'Fini'; // Fin du générateur
  } catch (error) {
    console.error('Une erreur est survenue :', error);
  }
}

// "Runner" simple pour le générateur (simplifié)
function run(generatorFunc) {
  const generator = generatorFunc();

  function handleNext(newValue) {
    const result = generator.next(newValue);

    if (!result.done) {
      if (result.value instanceof Promise) {
        result.value.then(handleNext).catch(handleError);
      } else {
        handleNext(result.value);
      }
    } else {
      console.log('Générateur terminé avec le résultat :', result.value);
    }
  }

  function handleError(error) {
      generator.throw(error);
  }

  handleNext(); // Démarrer le générateur
}

// Exécuter notre générateur asynchrone
run(myAsyncGenerator);

Dans cet exemple, yield Promise.resolve(1) suspend le générateur. Le "runner" (ou co) attend que la promesse soit résolue, puis reprend le générateur en passant la valeur 1 via next(1). Cette valeur 1 devient le résultat de yield Promise.resolve(1) à l’intérieur du générateur, permettant de le traiter dans la ligne suivante (console.log). Ce mécanisme permet d’imiter un flux d’exécution synchrone pour des opérations asynchrones.