Sobes.tech
Middle+

Waarom stellen gewone generators het schrijven van asynchrone code mogelijk?

sobes.tech AI

Antwoord van AI

De gewone generators in JavaScript maken het mogelijk om asynchrone code te schrijven dankzij hun vermogen om de uitvoering te pauzeren en te hervatten, evenals waarden in beide richtingen te verzenden (via yield en next()).

Hier is hoe het werkt in de context van asynchroniteit:

  1. Pauzeren van de uitvoering (yield): Wanneer de yield-instructie wordt bereikt, wordt de generator gepauzeerd en geeft een waarde terug. In asynchrone code kan deze waarde een belofte (promise) of een andere "actie" zijn die asynchroon moet worden uitgevoerd.

  2. Wachten op het asynchrone resultaat: De externe code (bijvoorbeeld een "runner" of een wrapper voor de generator) ontvangt de waarde die via yield is doorgegeven. Als het een belofte is, wacht hij op de resolutie of afwijzing ervan.

  3. Hervatten van de uitvoering (next()): Na het voltooien van de asynchrone operatie (belofte-resolutie), wordt het asynchrone resultaat (de waarde van de belofte) teruggegeven aan de generator via de methode next(). De generator hervat de uitvoering vanaf waar hij was gepauzeerd.

  4. Overdracht van het resultaat: De waarde die aan next() wordt doorgegeven, wordt het resultaat van de uitdrukking yield binnen de generator. Op deze manier kan de generator het resultaat van de asynchrone operatie "ontvangen" en ermee verder werken alsof de code synchrone is.

Dit patroon creëert een illusie van synchrone uitvoering voor asynchrone operaties, waardoor de code beter leesbaar en beheersbaar wordt in vergelijking met het traditionele gebruik van callbacks of complexe .then()-ketens.

Een voorbeeld hiervan zijn wrappers zoals co (hoewel nu meer gebruik wordt gemaakt van async/await, die in wezen syntactisch suiker zijn boven generators en beloftes):

// Voorbeeld van het gebruik van een generator voor asynchroniteit
function* myAsyncGenerator() {
  try {
    const result1 = yield Promise.resolve(1); // Pauzeert, geeft een belofte terug
    console.log('Eerste resultaat:', result1); // Hervat met de waarde

    const result2 = yield new Promise(resolve => setTimeout(() => resolve(2), 500)); // Nog een asynchrone operatie
    console.log('Tweede resultaat:', result2);

    return 'Klaar'; // Beëindigt de generator
  } catch (error) {
    console.error('Er is een fout opgetreden:', error);
  }
}

// Eenvoudige "runner" voor de generator (vereenvoudigd)
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('Generator voltooid met resultaat:', result.value);
    }
  }

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

  handleNext(); // Start de generator
}

// Voer onze asynchrone generator uit
run(myAsyncGenerator);

In dit voorbeeld pauzeert yield Promise.resolve(1) de generator. De "runner" (of co) wacht tot de belofte wordt opgelost en hervat vervolgens de generator door next(1) te gebruiken. Deze waarde 1 wordt het resultaat van yield Promise.resolve(1) binnen de generator, waardoor het op de volgende regel (console.log) kan worden verwerkt. Dit mechanisme stelt ons in staat om een synchrone stroom van uitvoering te imiteren voor asynchrone operaties.