Rajesh Babu

Follow

május 4, 2018 – 9 min read

Ha az elmúlt két-öt évben JavaScript fejlesztő voltál, akkor biztosan találkoztál olyan posztokkal, amelyek a generátorokról és az iterátorokról szóltak. Bár a generátorok és az Iterátorok eredendően összefüggnek, a generátorok egy kicsit félelmetesebbnek tűnnek, mint a másik.

Generátor Turbine

Az Iterátorok az Iterable objektumok, például térképek, tömbök és karakterláncok olyan implementációja, amely lehetővé teszi számunkra, hogy a next() segítségével iteráljuk őket. Sokféle felhasználási esetük van a generátorok, az Observables és a Spread operátorok között.

Azoknak, akiknek újak az iterátorok, az alábbi linket ajánlom: Guide to Iterators.

Hogy ellenőrizze, hogy az objektum megfelel-e az iterable protokollnak, ellenőrizze a beépített Symbol.iterator segítségével:

Az ES6 részeként bevezetett generátorok nem változtak a további JavaScript kiadásokban, és tovább maradnak itt. Úgy értem, tényleg sokáig! Tehát nincs menekvés előle. Bár az ES7 és az ES8 tartalmaz néhány új frissítést, ezek nem rendelkeznek olyan mértékű változással, mint az ES6 az ES5-höz képest, amely úgymond a következő szintre emelte a JavaScriptet.

Az írás végére biztos vagyok benne, hogy a függvénygenerátorok működésének alapos megértésével rendelkezni fogsz. Ha profi vagy, kérlek, segíts a tartalom javításában azzal, hogy a hozzászólásaidat a válaszok között teszed meg. Arra az esetre, ha nehezen tudnád követni a kódot, a legtöbb kódhoz magyarázatot is adtam, hogy jobban megértsd.

A JavaScriptben a függvények, mint tudjuk, “addig futnak, amíg vissza nem térnek/véget nem érnek”. A generátorfüggvények viszont “addig futnak, amíg yield/return/end”. A normál függvényektől eltérően a Generator Functions meghívás után visszaadja a Generator Object-et, amely a teljes Generator Iterable-t tartalmazza, amelyet a next() metódus vagy a for…of ciklus segítségével iterálhatunk.

A generátor minden next() hívása minden egyes kódsort végrehajt a következő yield-ig, amellyel találkozik, és ideiglenesen felfüggeszti a végrehajtást.

Szintaktikailag egy *-gal azonosítják őket, vagy függvény* X vagy függvény *X, – mindkettő ugyanazt jelenti.

A generátor függvény meghívása után a generátor objektumot adja vissza. Ezt a generátor objektumot egy változóhoz kell rendelni, hogy nyomon követhessük a rajta meghívott későbbi next() metódusokat. Ha a generátor objektumot nem rendeljük változóhoz, akkor mindig csak az első yield kifejezésig adódik minden next() esetén.

A generátor függvények általában yield kifejezésekkel épülnek fel. Minden yield a generátorfüggvényen belül egy megállási pont a következő végrehajtási ciklus megkezdése előtt. Az egyes végrehajtási ciklusokat a generátor next() metódusával indítjuk el.

A yield kifejezés minden next() híváskor visszaadja az értékét egy objektum formájában, amely a következő paramétereket tartalmazza.

{ value: 10, done: false } // assuming that 10 is the value of yield

  • Érték – minden, ami a yield kulcsszó jobb oldalára van írva, ez lehet függvényhívás, objektum vagy gyakorlatilag bármi. Üres yield esetén ez az érték meghatározatlan.
  • Done – a generátor állapotát jelzi, hogy a továbbiakban végrehajtható-e vagy sem. Ha a done true-t ad vissza, az azt jelenti, hogy a függvény befejezte a futását.

(Ha úgy érzi, hogy ez egy kicsit a feje fölött van, akkor az alábbi példa megtekintése után világosabb lesz…)

Bázis generátorfüggvény

Megjegyzés: A fenti példában a közvetlenül, wrapper nélkül elért generátorfüggvény mindig csak az első yield-ig hajtódik végre. Ezért a definíció szerint a generátort egy változóhoz kell rendelni, hogy megfelelően iterálhassunk rajta.

A generátorfüggvény életciklusa

Mielőtt továbbmennénk, vessünk egy gyors pillantást a generátorfüggvény életciklusának blokkdiagramjára:

Egy generátorfüggvény életciklusa

Ahányszor egy yieldtal találkozunk, a generátorfüggvény egy objektumot ad vissza, amely tartalmazza a találkozott yield értékét és a done állapotot. Hasonlóképpen, amikor visszatéréssel találkozunk, megkapjuk a visszatérési értéket és a done állapotot is igazként. Amikor a done állapotot true-ként kapjuk vissza, az lényegében azt jelenti, hogy a generátorfüggvény befejezte a futását, és további yield nem lehetséges.

Az első return után mindent figyelmen kívül hagyunk, beleértve a többi yield-kifejezést is.

A blokkdiagram jobb megértéséhez olvassunk tovább.

A yield hozzárendelése egy változóhoz

Az előző kódpéldában láttunk egy bevezetést az alapvető generátor létrehozásához yield-val. És megkaptuk a várt kimenetet. Most tegyük fel, hogy az alábbi kódban a teljes yield-kifejezést egy változóhoz rendeljük.

Assigning yield to a variable

Mi lesz az eredménye a változóhoz átadott teljes yield kifejezésnek ? Semmi vagy Meghatározatlan …

Miért ? A második next() függvénytől kezdve az előző yield kifejezés helyébe a következő függvényben átadott argumentumok lépnek. Mivel itt nem adunk át semmit a next metódusban, feltételezzük, hogy az egész ‘előző yield kifejezés’ mint undefined.

Ezt szem előtt tartva ugorjunk a következő szakaszba, hogy jobban megértsük az argumentumok átadását a next() metódusnak.

Passing Arguments to the next() Method

A fenti blokkdiagramra hivatkozva beszéljünk az argumentumok átadásáról a next függvényben. Ez az egyik legtrükkösebb része az egész generátor implementációnak.

Lássuk a következő kódrészletet, ahol a hozamot egy változóhoz rendeljük, de ezúttal értéket adunk át a next() metódusban.

Nézzük meg az alábbi kódot a konzolon. És a magyarázatot rögtön utána.

Az argumentumok átadása a next()

Magyarázat:

  1. Az első next(20) hívásakor az első yield-ig minden kódsor kiíródik. Mivel nincs korábbi yield kifejezésünk, ezt a 20-as értéket elvetjük. A kimeneten a yield értéket i*10-ként kapjuk, ami itt 100. A végrehajtás állapota is megáll az első yield-nál, és a const j még nincs beállítva.
  2. A második next(10) hívás a teljes első yield kifejezést 10-re cseréli, képzeljük el yield (i * 10) = 10, ami a továbbiakban a const j értékét 50-re állítja, mielőtt a második yield értékét visszaadja. A yield értéke itt 2 * 50 / 4 = 25.
  3. A harmadik next(5), a teljes második yield kifejezést 5-tel helyettesíti, így k értéke 5 lesz. És tovább folytatja a return utasítás végrehajtását, és visszaadja (x + y + z) => (10 + 50 + 5) = 65 mint végső hozamértéket a done true mellett.

Ez lehet, hogy az első olvasók számára kissé megterhelő, de szánjunk rá jó 5 percet, hogy újra és újra elolvassuk, hogy teljesen megértsük.

A yield átadása egy függvény argumentumaként

A yield körül n-számú felhasználási eset van arra vonatkozóan, hogy hogyan lehet használni egy függvénygenerátoron belül. Nézzük meg az alábbi kódban a yield egyik ilyen érdekes felhasználási módját, a magyarázattal együtt.

Yield mint egy függvény argumentuma

Magyarázat

  1. Az első next() határozatlan értéket ad, mert a yield kifejezésnek nincs értéke.
  2. A második next() az átadott értéket “haszontalan vagyok” adja ki. És előkészíti az argumentumot a függvényhíváshoz.
  3. A harmadik next(), meghívja a függvényt egy meghatározatlan argumentummal. Mint már említettük, az argumentum nélkül hívott next() metódus lényegében azt jelenti, hogy az egész előző yield kifejezés definiálatlan. Ezért ez kiírja a definiálatlant, és befejezi a futást.

Yield függvényhívással

A yield az értékek visszaadása mellett függvényeket is hívhat, és visszaadhatja az értéket vagy kiírhatja azt. Nézzük meg az alábbi kódot, hogy jobban megértsük.

Yield függvényhívással

A fenti kód a függvény visszatérési obj-ját adja vissza yield értékként. A futtatást pedig azzal fejezi be, hogy a const user értéke undefined lesz.

Yield ígéretekkel

A yield ígéretekkel ugyanaz a megközelítés, mint a fenti függvényhívás, ahelyett, hogy a függvény egy értéket adna vissza, egy ígéretet ad vissza, amelyet tovább lehet értékelni siker vagy sikertelenség esetén. Nézzük meg az alábbi kódot, hogy megértsük, hogyan működik:

Yield with promises

Az apiCall az ígéreteket yield értékként adja vissza, ha 2 másodperc után felbontják, kiírja a nekünk szükséges értéket.

Yield*

Az eddigiekben a yield kifejezés felhasználási eseteit néztük meg, most egy másik yield* nevű kifejezést fogunk megvizsgálni. A yield* egy generátorfüggvényen belül használva egy másik generátorfüggvényt delegál. Egyszerűen fogalmazva, szinkronban fejezi be a kifejezésben lévő generátorfüggvényt, mielőtt a következő sorra lépne.

Nézzük meg a kódot és az alábbi magyarázatot, hogy jobban megértsük. Ez a kód az MDN webes dokumentációjából származik.

Basic yield*

Magyarázat

  1. A next() első hívása 1 értéket ad.
  2. A második next() hívás azonban egy yield* kifejezés, ami természetszerűleg azt jelenti, hogy a yield* kifejezésben megadott másik generátorfüggvényt fogjuk befejezni, mielőtt folytatnánk az aktuális generátorfüggvényt.
  3. Gondolatban feltételezhetjük, hogy a fenti kódot az alábbiak szerint helyettesítjük
function* g2() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}

Ezzel folytatjuk a generátor futásának befejezését. Van azonban a yield*-nak egy megkülönböztetett képessége, amit érdemes szem előtt tartanod a return használatakor, a következő szakaszban.

Yield* returnnel

A yield* returnnel egy kicsit másképp viselkedik, mint a normál yield*. Amikor a yield*-ot return utasítással együtt használjuk, akkor az adott értékre értékelődik ki, vagyis a teljes yield* függvény() a kapcsolódó generátorfüggvényből visszaadott értékkel lesz egyenlő.

Nézzük meg a kódot és az alábbi magyarázatot, hogy jobban megértsük.

Yield* return

Magyarázat

  1. Az első next()-ben egyenesen a yield 1-hez megyünk, és annak értékét adjuk vissza.
  2. A második next() visszaadja a 2-t.
  3. A harmadik next(), visszaadja a ‘foo’ értéket, és továbbmegy a ‘the end’-hez, útközben hozzárendelve a ‘foo’ értéket a const eredményhez.
  4. Az utolsó next() befejezi a futást.

Yield* beépített iterálható objektummal

Még egy érdekes yield* tulajdonságot érdemes megemlíteni, a visszatérési értékhez hasonlóan a yield* is képes iterálni olyan iterálható objektumokon, mint az Array, String és Map.

Nézzük meg, hogyan működik ez valós időben.

Yield beépített iterálható objektumok felett

Itt a kódban a yield* minden lehetséges iterálható objektumon iterál, amit a kifejezésként átadunk. Gondolom, maga a kód magától értetődő.

Best Practices

Az összes iterátor/generátor felett iterálható egy for…of ciklus. Hasonlóan a next() metódusunkhoz, amelyet explicit módon hívunk meg, a for…of ciklus belsőleg a yield kulcsszó alapján lép tovább a következő iterációra. És csak az utolsó yield-ig iterál, és nem dolgozza fel a return utasításokat, mint a next() metódus.

Az alábbi kódban ellenőrizheti ugyanezt.

Yield a for…of

A végső visszatérési érték nem kerül kiírásra, mert a for…of ciklus csak az utolsó yield-ig iterál. Tehát a legjobb gyakorlatok közé tartozik, hogy kerüljük a return utasításokat egy generátorfüggvényen belül, mivel ez befolyásolná a függvény újrafelhasználhatóságát, ha egy for…of cikluson keresztül iteráljuk.

Következtetés

Remélem, ez lefedi a generátorfüggvények alapvető felhasználási eseteit, és őszintén remélem, hogy jobban megértettem, hogyan működnek a generátorok a JavaScript ES6-ban és afelett. Ha tetszett a tartalmam, kérem, hagyjon 1, 2, 3 vagy akár 50 tapsot :).

Kérem, kövessen a GitHub fiókomon további JavaScript és Full-Stack projektekért:

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.