31/03/2024
I den komplekse verden af moderne programmering, især inden for JavaScript, er evnen til at styre eksekveringsflowet afgørende. Mens de fleste funktioner kører fra start til slut i én uafbrudt sekvens, findes der en særlig mekanisme, der tillader os at sætte en funktion på pause og genoptage den senere. Dette kraftfulde værktøj er kendt som yield-operatoren, og den er hjertet i det, vi kalder generatorfunktioner. At forstå 'yield' er ikke blot at lære en ny syntaks; det er at åbne døren til avancerede mønstre for datahåndtering, iteration og asynkron programmering på en mere elegant og læsbar måde.

Hvad er en Generatorfunktion?
Før vi kan dykke helt ned i 'yield', må vi først forstå dens habitat: generatorfunktionen. En generatorfunktion er ikke en almindelig funktion. Den defineres med en stjerne efter `function`-nøgleordet (function* minGenerator() { ... }). Når du kalder en almindelig funktion, eksekverer den hele sin kode og returnerer en enkelt værdi. Når du derimod kalder en generatorfunktion, eksekverer den ikke med det samme. I stedet returnerer den et specielt objekt, kendt som en iterator.
Denne iterator er nøglen til at kontrollere funktionen. Den har en metode kaldet next(), og hver gang du kalder next(), kører koden inde i generatorfunktionen, indtil den støder på det næste 'yield'-udtryk. På dette tidspunkt pauser funktionen sin eksekvering, og værdien specificeret af 'yield' returneres til kalderen.
'yield'-operatorens Magi: Pause, Værdi og Genoptagelse
Tænk på `yield` som et intelligent `return`. Hvor `return` afslutter en funktion permanent, fungerer `yield` som et midlertidigt stopskilt. Når eksekveringen når et `yield`-udtryk, sker der to ting:
- Værdien af udtrykket efter `yield` sendes ud af funktionen.
- Funktionens tilstand (inklusive alle lokale variabler) fryses, og eksekveringen pauses præcis på det punkt.
Når next()-metoden kaldes igen, vågner funktionen op fra sin dvale og fortsætter eksekveringen lige efter det sted, hvor den sidst pausede. Denne cyklus kan gentages, indtil generatoren er færdig.
Resultatet fra hvert next()-kald er et objekt med to egenskaber:
value: Den værdi, der blev 'yielded' fra funktionen.done: En boolean, der erfalse, så længe generatoren stadig kan producere flere værdier (dvs. den er pauset ved et `yield`), ogtrue, når funktionen er fuldført.
Et simpelt eksempel:
function* tælTilTre() { console.log('Starter tælling'); yield 1; console.log('Fortsætter efter 1'); yield 2; console.log('Fortsætter efter 2'); yield 3; console.log('Tælling afsluttet'); } const minTæller = tælTilTre(); console.log(minTæller.next()); // { value: 1, done: false } og 'Starter tælling' logges console.log(minTæller.next()); // { value: 2, done: false } og 'Fortsætter efter 1' logges console.log(minTæller.next()); // { value: 3, done: false } og 'Fortsætter efter 2' logges console.log(minTæller.next()); // { value: undefined, done: true } og 'Tælling afsluttet' loggesTovejskommunikation med `next()`
En af de mest fascinerende aspekter ved generatorer er, at kommunikationen kan gå begge veje. Ikke alene kan generatoren 'yeilde' værdier ud, men vi kan også sende værdier *ind* i generatoren. Dette gøres ved at give en værdi som argument til next()-metoden, f.eks. iterator.next('en værdi').
Den værdi, du sender med `next()`, bliver returværdien af det `yield`-udtryk, hvor funktionen sidst pausede. Dette skaber en kraftfuld dialog mellem kalderen og generatorfunktionen.
Det er dog vigtigt at bemærke en asymmetri her: Det argument, der sendes til det *allerførste* `next()`-kald, går tabt. Dette skyldes, at der ikke er noget suspenderet `yield`-udtryk, der kan modtage værdien, når funktionen starter for første gang.
Eksempel på tovejskommunikation:
function* spørgsmålSvar() { const navn = yield 'Hvad er dit navn?'; console.log(`Hej, ${navn}!`); const hobby = yield 'Hvad er din hobby?'; console.log(`${hobby} lyder sjovt!`); return 'Samtale afsluttet'; } const samtale = spørgsmålSvar(); let svar1 = samtale.next(); // Starter generatoren. Første 'yield' nås. console.log(svar1.value); // 'Hvad er dit navn?' let svar2 = samtale.next('Lars'); // Sender 'Lars' ind. Det bliver værdien af 'navn'. console.log(svar2.value); // 'Hvad er din hobby?' let svar3 = samtale.next('Kodning'); // Sender 'Kodning' ind. Det bliver værdien af 'hobby'. console.log(svar3.value); // 'Samtale afsluttet' console.log(svar3.done); // trueSammenligning: Almindelig Funktion vs. Generatorfunktion
For at cementere forskellene er her en tabel, der sammenligner de to funktionstyper.
| Egenskab | Almindelig Funktion | Generatorfunktion |
|---|---|---|
| Eksekvering | Kører til fuldførelse i ét stræk (Run-to-completion). | Kan pauses og genoptages via next()-kald. |
| Returværdi ved kald | Den endelige returværdi fra funktionen. | En iterator-objekt. |
| Tilstand | Tilstanden (lokale variabler) nulstilles efter hvert kald. | Bevarer sin tilstand mellem pauser. |
| Nøgleord for output | return (afslutter funktionen). | yield (pauser) og return (afslutter). |
Afslutning af en Generator
En generatorfunktion behøver ikke at køre for evigt. Den kan afsluttes på flere måder, og hver gang vil next()-kaldet returnere et objekt, hvor done er true.
- Naturlig afslutning: Når eksekveringen når slutningen af funktionskroppen. I dette tilfælde vil det returnerede objekts
valueværeundefined. - Med `return`: En generator kan have en eksplicit `return`-sætning. Når den nås, afsluttes generatoren, og værdien fra `return` bliver
valuei det sidste resultatobjekt. - Med `throw`: Hvis der kastes en undtagelse (exception) inde i generatoren (enten med en `throw`-sætning eller på grund af en fejl), stoppes eksekveringen brat. Undtagelsen vil blive propageret ud til kalderen via
next()-kaldet.
Ofte Stillede Spørgsmål (FAQ)
Kan jeg bruge 'yield' i en almindelig funktion eller en arrow-funktion?
Nej. `yield`-nøgleordet kan kun bruges direkte inden i en generatorfunktion (en funktion defineret med `function*`). Forsøg på at bruge det andre steder, inklusiv i indlejrede funktioner inde i en generator, vil resultere i en syntaksfejl.
Hvad er den primære anvendelse for generatorer?
Generatorer er ekstremt alsidige. De bruges ofte til at skabe simple iteratorer over komplekse datastrukturer, håndtere asynkrone operationer på en mere læsbar måde (de var en forløber for `async/await`), generere uendelige sekvenser (f.eks. Fibonacci-tal) og implementere state machines.
Hvad er forskellen på `yield` og `yield*`?
`yield*` (med en stjerne) er en speciel form, der bruges til at delegere til en anden generator eller en itererbar objekt. I stedet for at 'yeilde' én værdi, vil `yield*` iterere over hele den anden generator/iterable og 'yeilde' hver af dens værdier en ad gangen. Det er en måde at 'indlejre' en generator i en anden.
Er generatorer langsommere end almindelige funktioner?
På grund af den ekstra overhead ved at skulle håndtere tilstand og pause/genoptage, kan et enkelt `next()`-kald være en smule langsommere end et almindeligt funktionskald. Men for de problemer, de er designet til at løse (som f.eks. at undgå at generere en kæmpe liste i hukommelsen på én gang), er de ofte langt mere effektive og hukommelsesvenlige.
Hvis du vil læse andre artikler, der ligner Forstå 'yield': Pausens Kraft i JavaScript, kan du besøge kategorien Teknologi.
