Rajesh Babu

Follow

4 mai, 2018 – 9 min citește

.

Dacă ați fost un dezvoltator JavaScript în ultimii doi-cinci ani, veți fi întâlnit cu siguranță postări care vorbesc despre Generatori și Iteratori. Deși Generatoarele și Iteratorii sunt asociate în mod inerent, Generatoarele par un pic mai intimidante decât celelalte.

Generator Turbine

Iteratorii sunt o implementare a obiectelor Iterable, cum ar fi hărțile, array-urile și șirurile de caractere, care ne permite să iterăm peste ele folosind next(). Aceștia au o mare varietate de cazuri de utilizare în Generators, Observables și Spread operators.

Recomand următorul link pentru cei care nu cunosc iteratorii, Guide to Iterators.

Pentru a verifica dacă obiectul dvs. este conform cu protocolul iterable, verificați folosind simbolul încorporat Symbol.iterator:

Generators introdus ca parte a ES6 nu a suferit nicio modificare pentru versiunile ulterioare ale JavaScript și sunt aici pentru a rămâne mai mult timp. Adică foarte mult timp! Așadar, nu se poate fugi de ele. Deși ES7 și ES8 au unele actualizări noi, acestea nu au aceeași magnitudine de schimbare pe care ES6 a avut-o față de ES5, care a dus JavaScript la următorul nivel, ca să spunem așa.

Până la sfârșitul acestui post, sunt sigur că veți avea o înțelegere solidă a modului în care funcționează Generatoarele de funcții. Dacă sunteți un profesionist, vă rog să mă ajutați să îmbunătățesc conținutul prin adăugarea comentariilor dvs. în răspunsuri. În cazul în care aveți dificultăți în a urmări codul, am adăugat, de asemenea, explicații pentru majoritatea codului pentru a vă ajuta să înțelegeți mai bine.

Funcțiile în JavaScript, după cum știm cu toții, „rulează până la return/end”. Funcțiile Generator, pe de altă parte, „rulează până la yield/return/end”. Spre deosebire de funcțiile normale, funcțiile Generator Functions, odată apelate, returnează obiectul Generator Object, care deține întregul Generator Iterable care poate fi iterat folosind metoda next() sau for…of loop.

Care apel next() pe generator execută fiecare linie de cod până la următorul yield pe care îl întâlnește și își suspendă temporar execuția.

Sintactic sunt identificate cu un *, fie funcția* X, fie funcția *X, – ambele semnifică același lucru.

După ce sunt create, apelarea funcției generatorului returnează obiectul Generator. Acest obiect generator trebuie să fie atribuit unei variabile pentru a ține evidența metodelor next() ulterioare apelate asupra sa. Dacă generatorul nu este atribuit unei variabile, atunci el va ceda întotdeauna doar până la prima expresie yield la fiecare next().

Funcțiile generator sunt construite în mod normal folosind expresii yield. Fiecare randament din interiorul funcției generatoare este un punct de oprire înainte de începerea următorului ciclu de execuție. Fiecare ciclu de execuție este declanșat prin intermediul metodei next() a generatorului.

La fiecare apel next(), expresia yield returnează valoarea sa sub forma unui obiect care conține următorii parametri.

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

  • Valoare – este tot ceea ce este scris în partea dreaptă a cuvântului cheie yield, poate fi un apel de funcție, un obiect sau practic orice. Pentru yield-uri goale, această valoare este nedefinită.
  • Done – indică starea generatorului, dacă poate fi executat în continuare sau nu. Atunci când done returnează true, înseamnă că funcția și-a terminat execuția.

(Dacă vi se pare că vă depășește puțin, veți avea mai multă claritate după ce veți vedea exemplul de mai jos…)

Funcție generatoare de bază

Note: În exemplul de mai sus, funcția generatoare accesată direct, fără un înveliș, se execută întotdeauna numai până la primul randament. Prin urmare, prin definiție, trebuie să atribuiți generatorul unei variabile pentru a itera corect peste el.

Ciclul de viață al unei funcții Generator

Înainte de a continua, să aruncăm o privire rapidă asupra diagramei bloc a ciclului de viață al funcției Generator:

Ciclul de viață al unei funcții generatoare

De fiecare dată când se întâlnește un randament, funcția generatoare returnează un obiect care conține valoarea randamentului întâlnit și starea „done”. În mod similar, atunci când se întâlnește un randament, se obține valoarea randamentului și, de asemenea, starea done ca fiind adevărată. Ori de câte ori statusul done este returnat ca fiind adevărat, înseamnă în esență că funcția generator și-a finalizat execuția și nu mai este posibil nici un alt randament.

Toate elementele de după primul return sunt ignorate, inclusiv alte expresii yield.

Citește mai departe pentru a înțelege mai bine diagrama bloc.

Asemnarea randamentului la o variabilă

În exemplul de cod anterior am văzut o introducere în crearea unui generator de bază cu un yield. Și am obținut rezultatul așteptat. Acum, să presupunem că atribuim întreaga expresie yield la o variabilă în codul de mai jos.

Asigning yield to a variable

Care este rezultatul întregii expresii yield trecute la variabilă ? Nimic sau nedefinit …

De ce ? Începând cu a doua next(), yield-ul anterior este înlocuit cu argumentele trecute în funcția următoare. Din moment ce nu trecem nimic aici în metoda next, se presupune că întreaga „expresie de randament anterioară” este nedefinită.

Cu acest lucru în minte, să trecem la următoarea secțiune pentru a înțelege mai multe despre trecerea argumentelor în metoda next().

Pasarea argumentelor în metoda next()

Cu referire la diagrama bloc de mai sus, să vorbim despre trecerea argumentelor în funcția next. Aceasta este una dintre cele mai dificile părți ale întregii implementări a generatorului.

Să luăm în considerare următoarea bucată de cod, în care randamentul este atribuit unei variabile, dar de data aceasta trecem o valoare în metoda next().

Să ne uităm la codul de mai jos în consolă. Și explicația imediat după aceea.

Pasarea de argumente în metoda next()

Explicație:

  1. Când apelăm prima metodă next(20), fiecare linie de cod până la primul yield este tipărită. Deoarece nu avem nicio expresie yield anterioară, această valoare 20 este eliminată. În ieșire obținem valoarea yield ca i*10, care este 100 aici. De asemenea, stadiul de execuție se oprește la primul randament și const j nu este încă setat.
  2. Cel de-al doilea apel next(10) înlocuiește întreaga expresie yield cu 10, imaginați-vă yield (i * 10) = 10, care continuă să seteze valoarea const j la 50 înainte de a returna valoarea celui de-al doilea randament. Valoarea yield aici este 2 * 50 / 4 = 25.
  3. Al treilea next(5), înlocuiește întreaga a doua expresie yield cu 5, aducând valoarea lui k la 5. Și în continuare continuă să execute instrucțiunea return și returnează (x + y + z) => (10 + 50 + 5) = 65 ca valoare finală a randamentului, împreună cu done true.

Acest lucru ar putea fi puțin copleșitor pentru cei care citesc pentru prima dată, dar acordați-vă 5 minute bune pentru a-l citi din nou și din nou pentru a înțelege complet.

Pasarea lui Yield ca argument al unei funcții

Există un număr n de cazuri de utilizare în jurul lui yield în ceea ce privește modul în care poate fi folosit în interiorul unui generator de funcții. Să ne uităm la codul de mai jos pentru o astfel de utilizare interesantă a yield, împreună cu explicația.

Yield ca argument al unei funcții

Explicație

  1. Primul next() produce o valoare nedefinită deoarece expresia yield nu are valoare.
  2. Cel de-al doilea next() produce „I am useless”, valoarea care a fost transmisă. Și pregătește argumentul pentru apelarea funcției.
  3. Cel de-al treilea next(), apelează funcția cu un argument nedefinit. După cum s-a menționat mai sus, metoda next() apelată fără niciun argument înseamnă, în esență, că întreaga expresie yield anterioară este nedefinită. Prin urmare, aceasta tipărește undefined și termină execuția.

Yield cu un apel de funcție

Pe lângă returnarea valorilor, yield poate, de asemenea, să apeleze funcții și să returneze valoarea sau să o tipărească. Să ne uităm la codul de mai jos și să înțelegem mai bine.

Yield call a function

Codul de mai sus returnează return obj al funcției ca valoare yield. Și încheie execuția prin setarea undefined la const user.

Yield with Promises

Yield with promises urmează aceeași abordare ca și apelul funcției de mai sus, în loc să returneze o valoare de la funcție, returnează o promisiune care poate fi evaluată mai departe pentru succes sau eșec. Să ne uităm la codul de mai jos pentru a înțelege cum funcționează.

Yield with promises

Apelul apiCall returnează promisiunile ca valoare de randament, atunci când este rezolvat după 2 secunde tipărește valoarea de care avem nevoie.

Yield*

Până acum am analizat cazurile de utilizare a expresiei yield, acum ne vom uita la o altă expresie numită yield*. Yield*, atunci când este utilizată în interiorul unei funcții generatoare, deleagă o altă funcție generatoare. Pur și simplu, aceasta finalizează în mod sincron funcția generatoare din expresia sa înainte de a trece la linia următoare.

Să ne uităm la codul și la explicația de mai jos pentru a înțelege mai bine. Acest cod provine din documentația web MDN.

Basic yield*

Explicație

  1. Primul apel next() produce o valoare 1.
  2. Cel de-al doilea apel next(), cu toate acestea, este o expresie yield*, ceea ce înseamnă în mod inerent că vom finaliza o altă funcție generatoare specificată în expresia yield* înainte de a continua funcția generatoare curentă.
  3. În mintea dumneavoastră, puteți presupune că codul de mai sus este înlocuit ca cel de mai jos
function* g2() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}

Acesta va continua pentru a termina execuția generatorului. Cu toate acestea, există o abilitate distinctă a yield* de care trebuie să țineți cont în timp ce folosiți return, în secțiunea următoare.

Yield* with Return

Yield* with a return se comportă puțin diferit față de yield* normal. Atunci când yield* este utilizat cu o instrucțiune return se evaluează la valoarea respectivă, ceea ce înseamnă că întreaga funcție yield*() devine egală cu valoarea returnată de funcția generatoare asociată.

Să ne uităm la codul și la explicația de mai jos pentru a înțelege mai bine.

Funcția yield* cu return

Explicație

  1. În primul next() mergem direct la yield 1 și returnăm valoarea acestuia.
  2. În al doilea next() se returnează 2.
  3. În al treilea next(), se returnează ‘foo’ și se trece la yield ‘the end’, atribuind ‘foo’ rezultatului const pe parcurs.
  4. Ultimul next() încheie execuția.

Yield* cu un obiect iterabil încorporat

Există încă o proprietate interesantă a lui yield* care merită menționată, similar cu valoarea de retur, yield* poate, de asemenea, să itereze peste obiecte iterabile precum Array, String și Map.

Să vedem cum funcționează în timp real.

Yield over built-in iterables

Aici, în cod, yield* iterază peste fiecare obiect iterabil posibil care este trecut ca expresie a sa. Cred că codul în sine se explică de la sine.

Bune practici

Pe lângă toate acestea, fiecare iterator/generator poate fi iterat peste o buclă for…of. Similar metodei noastre next(), care este apelată în mod explicit, bucla for…of trece în mod intern la următoarea iterație pe baza cuvântului cheie yield. Și itera doar până la ultimul yield și nu procesează declarațiile de returnare ca metoda next().

Puteți verifica același lucru în codul de mai jos.

Yield cu for…of

Valoarea finală de întoarcere nu este tipărită, deoarece bucla for…of iterază numai până la ultimul yield. Așadar, intră în categoria celor mai bune practici evitarea declarațiilor return în interiorul unei funcții generatoare, deoarece aceasta ar afecta capacitatea de reutilizare a funcției atunci când este iterată printr-o buclă for…of.

Concluzie

Sper că acest lucru acoperă cazurile de utilizare de bază ale funcțiilor generatoare și sper sincer că a oferit o mai bună înțelegere a modului în care funcționează generatoarele în JavaScript ES6 și superior. Dacă vă place conținutul meu, vă rog să lăsați 1, 2, 3 sau chiar 50 de aplauze :).

Vă rog să mă urmăriți pe contul meu GitHub pentru mai multe proiecte JavaScript și Full-Stack:

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.