Rajesh Babu

Seuraa

4. toukokuuta, 2018 – 9 min read

Jos olet ollut JavaScript-kehittäjä viimeisen kahden-viiden vuoden aikana, olet varmasti törmännyt viesteihin, joissa puhutaan generaattoreista ja iteraattoreista. Vaikka generaattorit ja Iteraattorit liittyvät luonnostaan toisiinsa, generaattorit tuntuvat hieman pelottavammilta kuin toinen.

Generator Turbine

Iteraattorit ovat Iterable-objektien, kuten karttojen, taulukoiden ja merkkijonojen, toteutus, jonka avulla voimme iteroida niiden yli next()-funktiolla. Niillä on monenlaisia käyttötapauksia generaattoreissa, observableissa ja spread-operaattoreissa.

Suosittelen seuraavaa linkkiä niille, joille iteraattorit ovat uusia, Guide to Iterators.

Tarkistaaksesi, onko objektisi iterable-protokollan mukainen, tarkista se käyttämällä sisäänrakennettua Symbol.iterator:

Es6:n yhteydessä käyttöönotetut generaattorit eivät ole kokeneet muutoksia myöhemmissä JavaScript-julkaisuissakaan, ja ne ovatkin tulleet tänne pysyäkseen täällä pidempään. Siis todella pitkään! Joten sitä ei voi paeta. Vaikka ES7:ssä ja ES8:ssa on joitain uusia päivityksiä, niissä ei ole yhtä suuria muutoksia kuin ES6:ssa ES5:een verrattuna, joka niin sanotusti vei JavaScriptin seuraavalle tasolle.

Tämän postauksen loppuun mennessä olen varma, että sinulla on vankka käsitys siitä, miten funktiogeneraattorit toimivat. Jos olet ammattilainen, auta minua parantamaan sisältöä lisäämällä kommenttisi vastauksiin. Siltä varalta, että sinulla on vaikeuksia seurata koodia, olen myös lisännyt selityksen suurimmalle osalle koodia, jotta ymmärtäisit paremmin.

Javaskriptin funktiot, kuten kaikki tiedämme, ”toimivat kunnes paluu/loppu tulee”. Generaattoritoiminnot sen sijaan ”toimivat kunnes yield/return/end”. Toisin kuin tavalliset funktiot Generator Functions palauttaa kutsuttuaan Generator Objectin, joka sisältää koko Generator Iterable -olion, jota voidaan iteroida käyttämällä next()-metodia tai for…of-silmukkaa.

Jokainen next()-kutsu generaattorissa suorittaa jokaisen koodirivin aina seuraavaan kohtaamaansa tuottoon asti ja keskeyttää suorituksensa väliaikaisesti.

Syntaktisesti ne tunnistetaan *:lla, joko funktio* X tai funktio *X, – molemmat tarkoittavat samaa asiaa.

Kun generaattorifunktio on luotu, sen kutsuminen palauttaa generaattoriobjektin. Tämä generaattori-objekti on osoitettava muuttujaan, jotta voidaan seurata myöhempiä next()-metodeja, joita kutsutaan itseensä. Jos generaattoria ei osoiteta muuttujaan, se tuottaa aina vain ensimmäiseen yield-lausekkeeseen asti jokaisella next():lla.

Generator-funktiot rakennetaan normaalisti käyttämällä yield-lausekkeita. Jokainen yield generaattorifunktion sisällä on pysähdyspiste ennen kuin seuraava suoritusjakso alkaa. Jokainen suoritussykli käynnistyy generaattorin next()-metodin avulla.

Kullakin next()-kutsulla yield-lauseke palauttaa arvonsa objektina, joka sisältää seuraavat parametrit.

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

  • Arvo – on kaikki, mikä on kirjoitettu yield-avainsanan oikealle puolelle, se voi olla funktiokutsu, objekti tai käytännössä mitä tahansa. Tyhjien tuottojen kohdalla tämä arvo on määrittelemätön.
  • Done – ilmaisee generaattorin tilan, voidaanko sitä suorittaa edelleen vai ei. Kun done palauttaa arvon true, se tarkoittaa, että funktio on suorittanut suorituksensa loppuun.

(Jos sinusta tuntuu, että tämä on hieman yli ymmärryksesi, saat lisää selkeyttä, kun näet alla olevan esimerkin…)

Perusgeneraattorifunktio

Huom: Yllä olevassa esimerkissä generaattorifunktio, jota käytetään suoraan ilman kääreitä, suoritetaan aina vain ensimmäiseen tuottoon asti. Näin ollen generaattorifunktio on määritelmän mukaan osoitettava muuttujaan, jotta sitä voidaan oikein iteroida.

GENERAATTORIFUNKTION ELÄMÄNKULKU

Ennen kuin jatkamme eteenpäin, tarkastellaan lyhyesti generaattorifunktion elinkaaren lohkokaaviota:

GENERAATTORIFUNKTION ELÄMÄNKULKU

Joka kerta, kun kohdataan saanto, generaattorifunktio palauttaa objektin, joka sisältää kohdatun saannon arvon ja done-tilan. Vastaavasti, kun kohtaamme palautuksen, saamme palautusarvon ja myös done-tilan totena. Aina kun done-tila palautetaan totena, se tarkoittaa lähinnä sitä, että generaattorifunktio on suorittanut suorituksensa loppuun, eikä mikään muu yield ole enää mahdollinen.

Kaikkea ensimmäisen returnin jälkeistä ei huomioida, mukaan lukien muut yield-lausekkeet.

Lue lisää ymmärtääksesi lohkokaavion paremmin.

Yieldin osoittaminen muuttujalle

Edellisessä koodiesimerkissä näimme johdatuksen perusgeneraattoriin, jossa on yield. Ja saimme odotetun tuloksen. Oletetaan nyt, että annamme koko yield-lausekkeen muuttujalle alla olevassa koodissa.

Assigning yield to a variable

Mikä on koko yield-lausekkeen tulos, joka on siirretty muuttujaan ? Ei mitään tai Määrittelemätön …

Miksi ? Toisesta next()-funktiosta alkaen edellinen yield korvataan seuraavassa funktiossa välitetyillä argumenteilla. Koska emme välitä mitään tässä seuraavassa metodissa, sen oletetaan, että koko ’edellinen tuottolauseke’ on määrittelemätön.

Tämä mielessä, hypätään seuraavaan osioon ymmärtämään lisää argumentin välittämisestä seuraavaan() metodiin.

Argumenttien välittäminen seuraavaan() metodiin

Viitaten yllä olevaan lohkokaavioon puhutaan argumenttien välittämisestä seuraavaan funktioon. Tämä on yksi hankalimmista osista koko generaattorin toteutuksessa.

Katsotaanpa seuraavaa koodinpätkää, jossa yield osoitetaan muuttujaan, mutta tällä kertaa välitämme arvon next()-metodiin.

Katsotaanpa alla olevaa koodia konsolissa. Ja selitys heti sen jälkeen.

Argumenttien välittäminen next()

Erittely:

  1. Kun kutsumme ensimmäistä next(20) -metodia, tulostetaan jokainen koodirivi ensimmäiseen yieldiin asti. Koska meillä ei ole aiempaa yield-lauseketta, tämä arvo 20 hylätään. Tulosteessa saamme tuotto-arvoksi i*10, joka on tässä 100. Myös suorituksen tila pysähtyy ensimmäiseen yieldiin eikä const j:tä ole vielä asetettu.
  2. Toinen next(10)-kutsu korvaa koko ensimmäisen yield-lausekkeen arvolla 10, kuvittele yield (i * 10) = 10, joka asettaa const j:n arvoksi 50 ennen toisen yieldin arvon palauttamista. Tuottoarvo on tässä 2 * 50 / 4 = 25.
  3. Kolmas next(5), korvaa koko toisen yield-lausekkeen arvolla 5, jolloin k:n arvoksi tulee 5. Ja edelleen jatketaan return-lauseen suorittamista ja palautetaan (x + y + z) => (10 + 50 + 5) = 65 lopullisena saantoarvona yhdessä done true:n kanssa.

Tämä saattaa olla ensilukijalle hiukan häkellyttävää, mutta kannattaa käyttää reilut 5 minuuttia lukemiseen uudestaan ja uudestaan ymmärtääkseen sen täysin.

Yieldin välittäminen funktion argumenttina

Yieldin ympärillä on n-määrä käyttötapauksia siitä, miten sitä voidaan käyttää funktiogeneraattorin sisällä. Katsotaanpa alla olevassa koodissa yksi tällainen mielenkiintoinen yieldin käyttötapa selityksen kera.

Yield funktion argumenttina

selitys

  1. Ensimmäinen next() tuottaa määrittelemättömän arvon, koska yield-lausekkeella ei ole arvoa.
  2. Toinen next() tuottaa arvon ”I am usless”, joka oli välitetty. Ja valmistelee argumentin funktion kutsua varten.
  3. Kolmas next(), kutsuu funktiota määrittelemättömällä argumentilla. Kuten edellä mainittiin, next()-metodi, jota kutsutaan ilman argumentteja, tarkoittaa lähinnä sitä, että koko edellinen yield-lauseke on määrittelemätön. Näin ollen tämä tulostaa määrittelemättömän ja lopettaa suorituksen.

Yield funktiokutsulla

Arvojen palauttamisen lisäksi yield voi myös kutsua funktioita ja palauttaa arvon tai tulostaa sen. Katsotaan alla olevaa koodia ja ymmärretään paremmin.

Yield kutsumalla funktiota

Ylläoleva koodi palauttaa funktiosta palautettavan obj:n yield-arvona. Ja lopettaa suorituksen asettamalla const-käyttäjän arvoksi undefined.

Yield lupausten avulla

Yield lupausten avulla noudattaa samaa lähestymistapaa kuin yllä oleva funktiokutsu, mutta sen sijaan, että funktio palauttaisi arvon, se palauttaa lupauksen, jota voidaan edelleen arvioida onnistumisen tai epäonnistumisen varalta. Katsotaanpa alla olevaa koodia, jotta ymmärretään, miten se toimii.

Yield with promises

ApiCall palauttaa lupauksen tuotto-arvona, kun se ratkaistaan 2 sekunnin kuluttua, tulostaa tarvitsemamme arvon.

Yield*

Tähän mennessä olemme tarkastelleet yield-lausekkeen käyttötapauksia, nyt tarkastelemme toista lauseketta nimeltä yield*. Yield*, kun sitä käytetään generaattorifunktion sisällä, delegoi toisen generaattorifunktion. Yksinkertaisesti sanottuna se suorittaa synkronisesti loppuun lausekkeessa olevan generaattorifunktion ennen siirtymistä seuraavalle riville.

Katsotaanpa koodia ja alla olevaa selitystä, jotta ymmärrämme paremmin. Tämä koodi on MDN-verkkodokumenteista.

Basic yield*

Erittely

  1. Ensimmäinen next()-kutsu antaa tulokseksi arvon 1.
  2. Toinen next()-kutsu on kuitenkin yield*-lauseke, mikä tarkoittaa luonnostaan sitä, että suoritamme loppuun toisen yield*-lausekkeessa määritellyn generaattoritoiminnon ennen kuin jatkamme nykyistä generaattoritoimintoa.
  3. Mielessäsi voit olettaa, että yllä oleva koodi korvataan alla olevan kaltaiseksi
function* g2() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}

Tämä jatkuu generaattorin suorituksen loppuun. Yield*:lla on kuitenkin yksi selvä kyky, joka kannattaa pitää mielessä returnia käytettäessä, seuraavassa kappaleessa.

Yield* with Return

Yield* returnin kanssa käyttäytyy hieman eri tavalla kuin tavallinen yield*. Kun yield*:tä käytetään return-lausekkeen kanssa, se evaluoituu kyseiseen arvoon, eli koko yield*-funktio() muuttuu yhtä suureksi kuin siihen liittyvän generaattorifunktion palauttama arvo.

Katsotaanpa koodia ja alla olevaa selitystä, jotta ymmärrämme sen paremmin.

Yield* with return

selitys

    1. Ensimmäisessä next()-funktiossa siirrymme suoraan yield-funktioon 1 ja palautamme sen arvon.
    2. Toinen next() tuottaa 2.
    3. Kolmas next(), palauttaa ’foo’ ja menee tuottamaan ’the end’, osoittaen ’foo’ const-tulokseen matkalla.
    4. Viimeinen next() päättää suorituksen.

    Yield* sisäänrakennetulla iteroitavalla objektilla

    On vielä yksi mielenkiintoinen yield*-ominaisuus, joka on mainitsemisen arvoinen, paluuarvon tavoin yield* voi myös iteroida iteroitavien objektien, kuten Array:n, String:n ja Map:n yli.

    Katsotaanpa, miten se toimii reaaliajassa.

    Yield yli sisäänrakennettujen iteroitavien objektien

    Tässä koodissa yield*:n avulla iteroidaan jokaisen mahdollisen iteroitavan objektin yli, joka välitetään lausekkeena. Luulen, että koodi itsessään on itsestään selvä.

    Parhaat käytännöt

    Tämän kaiken lisäksi jokaisen iteraattorin/generaattorin yli voidaan iteroida for…of-silmukalla. Samoin kuin next()-metodimme, jota kutsutaan eksplisiittisesti, for…of-silmukka siirtyy sisäisesti seuraavaan iteraatioon yield-avainsanan perusteella. Ja se iteroi vain viimeiseen yieldiin asti eikä käsittele return-lauseita kuten next()-metodi.

    Voit todentaa saman alla olevasta koodista.

    Yield for…of:lla

    Loppupaluuarvoa ei tulosteta, koska for…of-silmukka iteroi vain viimeiseen tuottoon asti. Näin ollen parhaiden käytäntöjen joukkoon kuuluu välttää return-lausekkeita generaattorifunktion sisällä, koska se vaikuttaisi funktion uudelleenkäytettävyyteen, kun sitä iteroidaan for…of-silmukalla.

    Loppupäätelmä

    Toivottavasti tämä kattaa generaattorifunktioiden peruskäyttökohteet ja toivon vilpittömästi, että annoin paremman ymmärryksen generaattoreiden toiminnasta JavaScript ES6:ssa ja sitä uudemmissa versioissa. Jos pidät sisällöstäni, jätä 1, 2, 3 tai jopa 50 taputusta :).

    Seuraa minua GitHub-tililläni saadaksesi lisää JavaScript- ja Full-Stack-projekteja:

Vastaa

Sähköpostiosoitettasi ei julkaista.