Iterators zijn een implementatie van Iterable-objecten zoals mappen, arrays en strings die ons in staat stellen om over hen te itereren met behulp van next(). Ze hebben een grote verscheidenheid aan gebruikssituaties in Generators, Observables en Spread operators.
Ik raad de volgende link aan voor degenen onder u die nieuw zijn met iterators, Guide to Iterators.
Om te controleren of uw object voldoet aan het iterable-protocol, controleert u met behulp van het ingebouwde Symbol.iterator:
Generators die als onderdeel van ES6 zijn geïntroduceerd, hebben geen wijzigingen ondergaan voor de verdere JavaScript-releases en ze zijn hier om langer te blijven. Ik bedoel echt lang! Er valt dus niet van weg te lopen. Hoewel ES7 en ES8 enkele nieuwe updates hebben, hebben ze niet dezelfde omvang van verandering die ES6 had ten opzichte van ES5, die JavaScript naar het volgende niveau bracht, om het zo te zeggen.
Aan het eind van dit bericht ben ik er zeker van dat je een solide begrip zult hebben van hoe functiegeneratoren werken. Als je een pro bent, help me dan de inhoud te verbeteren door je commentaar in de reacties te zetten. Voor het geval dat je moeite hebt met het volgen van de code, heb ik ook uitleg toegevoegd voor de meeste code om je te helpen het beter te begrijpen.
Functies in JavaScript, zoals we allemaal weten, “draaien tot terugkeer/einde”. Generatorfuncties daarentegen “draaien tot yield/return/beëindigen”. In tegenstelling tot normale functies geven Generator Functions, zodra ze worden aangeroepen, het Generator Object terug, dat de volledige Generator Iterable bevat die kan worden doorlopen met de methode next() of for…of loop.
Elke aanroep van next() op de generator voert elke regel code uit tot de volgende return die hij tegenkomt en schort de uitvoering tijdelijk op.
Syntactisch worden ze aangeduid met een *, ofwel functie* X of functie *X, – beide betekenen hetzelfde.
Eenmaal gemaakt, retourneert het aanroepen van de generatorfunctie het Generator-object. Dit generator object moet worden toegewezen aan een variabele om bij te houden welke next() methodes daarna op zichzelf worden aangeroepen. Als de generator niet aan een variabele wordt toegewezen, zal deze altijd slechts tot de eerste yield expressie bij elke next().
De generator functies worden normaal gesproken opgebouwd met behulp van yield expressies. Elke yield in de generatorfunctie is een stoppunt voordat de volgende uitvoeringscyclus begint. Elke uitvoeringscyclus wordt getriggerd door middel van de methode next() op de generator.
Bij elke aanroep van next() geeft de yield-expressie zijn waarde terug in de vorm van een object dat de volgende parameters bevat.
{ value: 10, done: false } // assuming that 10 is the value of yield
- Waarde – is alles wat aan de rechterkant van het yield-sleutelwoord staat, het kan een functieaanroep zijn, een object of praktisch alles. Voor lege yields is deze waarde ongedefinieerd.
- Done – geeft de status van de generator aan, of hij verder uitgevoerd kan worden of niet. Als done true teruggeeft, betekent dit dat de functie klaar is met zijn uitvoering.
(Als je het gevoel hebt dat dit een beetje boven je pet gaat, zul je meer duidelijkheid krijgen zodra je het onderstaande voorbeeld ziet…)
Note: In het bovenstaande voorbeeld wordt de generatorfunctie die rechtstreeks zonder wrapper wordt benaderd, altijd alleen uitgevoerd tot de eerste yield. Daarom moet je de generator per definitie toewijzen aan een variabele om er op de juiste manier over te itereren.
Life-cycle van een Generator Functie
Voordat we verder gaan, laten we even een blik werpen op het blokschema van de levenscyclus van de Generator Functie:
Telkens wanneer een yield wordt aangetroffen, retourneert de generator-functie een object dat de waarde van de aangetroffen yield en de done-status bevat. Op dezelfde manier, wanneer een return wordt gevonden, krijgen we de return-waarde en ook de done-status als true. Wanneer de done-status als true wordt geretourneerd, betekent dit in wezen dat de generatorfunctie zijn bewerking heeft voltooid en dat er geen verdere yield mogelijk is.
Alles na de eerste return wordt genegeerd, inclusief andere yield-expressies.
Lees verder om het blokschema beter te begrijpen.
Yield aan een variabele toewijzen
In het vorige codevoorbeeld zagen we een inleiding tot het maken van een basisgenerator met een yield. En we kregen de verwachte output. Laten we nu eens aannemen dat we de volledige yield-uitdrukking toewijzen aan een variabele in de onderstaande code.
Wat is het resultaat van de volledige yield-uitdrukking die aan de variabele wordt doorgegeven? Niets of ongedefinieerd …
Waarom? Vanaf de tweede next(), wordt de vorige yield vervangen door argumenten die in de volgende functie worden doorgegeven. Omdat we hier niets doorgeven in de volgende methode, wordt aangenomen dat de hele ‘vorige-yield expressie’ ongedefinieerd is.
Met dit in gedachten, laten we naar de volgende sectie springen om meer te begrijpen over het doorgeven van argumenten aan de volgende() methode.
Argumenten doorgeven aan de volgende() methode
Met verwijzing naar het bovenstaande blokschema, laten we het hebben over het doorgeven van argumenten in de volgende functie. Dit is een van de lastigste onderdelen van de hele generator implementatie.
Laten we eens kijken naar het volgende stukje code, waar de opbrengst wordt toegewezen aan een variabele, maar deze keer geven we een waarde door in de next() methode.
Laten we eens kijken naar de code hieronder in de console. En de uitleg direct daarna.
Uitleg:
- Wanneer we de eerste next(20) aanroepen, wordt elke regel code tot de eerste yield afgedrukt. Omdat we geen eerdere yield expressie hebben, wordt deze waarde 20 weggegooid. In de uitvoer krijgen we de opbrengstwaarde als i*10, wat hier 100 is. Ook stopt de status van de uitvoering met de eerste yield en de const j is nog niet ingesteld.
- De tweede next(10) call, vervangt de hele eerste yield expressie door 10, stel je voor yield (i * 10) = 10, die verder gaat om de waarde van const j op 50 te zetten voordat de waarde van de tweede yield wordt teruggegeven. De waarde van de yield is hier 2 * 50 / 4 = 25.
- Derde next(5), vervangt de gehele tweede yield door 5, waardoor de waarde van k op 5 komt. En gaat verder met het uitvoeren van de return-instructie en retourneert (x + y + z) => (10 + 50 + 5) = 65 als de uiteindelijke yield-waarde, samen met done true.
Dit is misschien een beetje overweldigend voor lezers die het voor het eerst lezen, maar neem ruim 5 minuten de tijd om het steeds opnieuw te lezen om het volledig te begrijpen.
Yield doorgeven als een argument van een functie
Er zijn n-aantal use-cases rond yield met betrekking tot hoe het kan worden gebruikt in een functiegenerator. Laten we eens kijken naar de code hieronder voor zo’n interessant gebruik van yield, samen met de uitleg.
Uitleg
- De eerste next() levert een ongedefinieerde waarde op omdat de yield expressie geen waarde heeft.
- De tweede volgende() levert “Ik ben nutteloos” op, de waarde die werd doorgegeven. En bereidt het argument voor op de functie-aanroep.
- De derde next(), roept de functie aan met een ongedefinieerd argument. Zoals hierboven vermeld, betekent de next() methode, aangeroepen zonder argumenten, in wezen dat de hele vorige yield uitdrukking ongedefinieerd is. Dit drukt dus ongedefinieerd af en beëindigt de run.
Yield met een functie-aanroep
Naast het retourneren van waarden kan yield ook functies aanroepen en de waarde retourneren of dezelfde afdrukken. Laten we onderstaande code eens bekijken en het beter begrijpen.
De bovenstaande code retourneert de obj van de functie als de waarde van het rendement.
Yield met beloften
Yield met beloften volgt dezelfde aanpak als de functie-oproep hierboven, maar in plaats van een waarde uit de functie terug te geven, wordt een belofte teruggegeven die verder kan worden geëvalueerd op succes of mislukking. Laten we eens kijken naar de onderstaande code om te begrijpen hoe het werkt.
De apiCall retourneert de beloften als de yield-waarde, wanneer deze na 2 seconden wordt opgelost, drukt deze de waarde af die we nodig hebben.
Yield*
Tot nu toe hebben we gekeken naar de use-cases voor yield expressie, nu gaan we kijken naar een andere expressie genaamd yield*. Wanneer Yield* gebruikt wordt in een generator functie delegeert het een andere generator functie. Simpel gezegd, het voltooit synchroon de generator functie in zijn expressie alvorens verder te gaan naar de volgende regel.
Laten we eens kijken naar de code en de uitleg hieronder om het beter te begrijpen. Deze code is afkomstig van de MDN-webdocs.
Uitleg
- De eerste aanroep van next() levert een waarde van 1 op.
- De tweede next() aanroep is echter een yield* expressie, wat inherent betekent dat we een andere generator functie, gespecificeerd in de yield* expressie, gaan voltooien alvorens verder te gaan met de huidige generator functie.
- In uw gedachten kunt u aannemen dat de bovenstaande code wordt vervangen zoals de onderstaande
function* g2() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
Dit zal verder gaan om de generator run te voltooien. Er is echter één eigenschap van de yield* die u in gedachten moet houden bij het gebruik van return, in de volgende sectie.
Yield* met return
Yield* met een return gedraagt zich een beetje anders dan de normale yield*. Wanneer yield* wordt gebruikt met een return-instructie, wordt deze geëvalueerd naar die waarde, wat betekent dat de hele yield*-functie() gelijk wordt aan de waarde die is teruggekeerd van de bijbehorende generatorfunctie.
Laten we eens kijken naar de code en de uitleg hieronder om het beter te begrijpen.
Uitleg
- In de eerste next() gaan we direct naar de yield 1 en geven de waarde terug.
- De tweede volgende() geeft 2.
- De derde volgende(), geeft ‘foo’ terug en gaat verder naar de opbrengst ‘het einde’, onderweg ‘foo’ toewijzend aan het const resultaat.
- De laatste next() maakt de run af.
Yield* met een ingebouwd Iterable Object
Er is nog een interessante eigenschap van yield* die het vermelden waard is, vergelijkbaar met de return waarde, kan yield* ook itereren over iterable objecten zoals Array, String en Map.
Laten we eens kijken hoe het in real-time werkt.
Hier in de code itereert de yield* over elk mogelijk iterable object dat als expressie wordt doorgegeven. Ik denk dat de code zelf voor zichzelf spreekt.
Best Practices
Overal kan elke iterator/generator worden ge¨ıtereerd over een for…of-lus. Net als onze next() methode die expliciet wordt aangeroepen, gaat een for…of-lus intern door naar de volgende iteratie op basis van het yield sleutelwoord. En hij gaat alleen door tot de laatste yield en verwerkt niet de return statements zoals de next() method.
Je kunt hetzelfde controleren in de code hieronder.
De uiteindelijke terugkeerwaarde wordt niet afgedrukt, omdat de for…of-lus alleen tot de laatste yield itereert. Het is dus een goede gewoonte om return-verklaringen binnen een generatorfunctie te vermijden, omdat dit de herbruikbaarheid van de functie zou beïnvloeden wanneer deze door een for…of-lus wordt ge-erteerd.
Conclusie
Ik hoop dat dit de basisgebruikscases van generatorfuncties heeft behandeld en ik hoop oprecht dat het een beter begrip heeft gegeven van hoe generatoren werken in JavaScript ES6 en hoger. Als je van mijn inhoud laat 1, 2, 3 of zelfs 50 klappen :).
Volg mij op mijn GitHub account voor meer JavaScript en Full-Stack projecten: