Funkce v JavaScriptu, jak všichni víme, „běží do návratu/konce“. Generátorové funkce naproti tomu „běží až do yield/return/end“. Na rozdíl od běžných funkcí funkce generátoru po zavolání vrací objekt generátoru, který obsahuje celou generátorovou iterovatelnou množinu, kterou lze iterovat pomocí metody next() nebo cyklu for…of.
Každé volání metody next() na generátoru provede každý řádek kódu až do dalšího yield, na který narazí, a dočasně pozastaví jeho provádění.
Syntakticky se označují znakem *, buď funkce* X, nebo funkce *X, – obojí znamená totéž.
Po vytvoření volání funkce generátoru se vrátí objekt generátoru. Tento objekt generátoru je třeba přiřadit proměnné, aby bylo možné sledovat následné metody next() volané na něm samém. Pokud není generátor přiřazen k proměnné, pak bude při každé funkci next() dávat vždy jen do prvního výrazu yield.
Funkce generátoru se obvykle vytvářejí pomocí výrazů yield. Každý yield uvnitř generátorové funkce je bodem zastavení před zahájením dalšího cyklu provádění. Každý cyklus provádění se v generátoru spouští pomocí metody next().
Při každém volání next() vrací yield výraz svou hodnotu v podobě objektu obsahujícího následující parametry.
{ value: 10, done: false } // assuming that 10 is the value of yield
Hodnota – je vše, co je zapsáno na pravé straně klíčového slova yield, může to být volání funkce, objekt nebo prakticky cokoli. U prázdných yieldů je tato hodnota nedefinovaná.
Hotovo – udává stav generátoru, zda může být dále prováděn, nebo ne. Pokud done vrací true, znamená to, že funkce dokončila svůj běh.
(Pokud máte pocit, že je to trochu nad vaše síly, bude vám to jasnější, až uvidíte příklad níže…)
Základní funkce generátoru
Poznámka: Ve výše uvedeném příkladu se generátorová funkce, ke které se přistupuje přímo bez wrapperu, vykoná vždy jen do prvního yield. Proto je z definice nutné přiřadit funkci Generator proměnné, abyste nad ní mohli správně iterovat.
Životní cyklus funkce generátoru
Než budeme pokračovat dále, podívejme se na blokové schéma životního cyklu funkce generátoru:
Životní cyklus funkce generátoru
Při každém setkání s výnosem vrací funkce generátoru objekt obsahující hodnotu setkaného výnosu a stav hotovo. Podobně při setkání s výnosem dostaneme návratovou hodnotu a také stav hotovo jako true. Kdykoli je stav done vrácen jako true, znamená to v podstatě, že funkce generátoru dokončila svůj běh a žádný další yield již není možný.
Všechno po prvním returnu je ignorováno, včetně dalších výrazů yield.
Pro lepší pochopení blokového schématu čtěte dále.
Přiřazení yield proměnné
V předchozí ukázce kódu jsme viděli úvod do vytváření základního generátoru s yield. A získali očekávaný výstup. Nyní předpokládejme, že v následujícím kódu přiřadíme proměnné celý výraz yield.
Přiřazení yield proměnné
Jaký je výsledek celého výrazu yield předaného proměnné ? Nic nebo neurčeno …
Proč ? Počínaje druhou funkcí next() je předchozí yield nahrazen argumenty předanými v další funkci. Protože zde v metodě next nepředáváme nic, předpokládá se, že celý „předchozí výraz yield“ je nedefinovaný.
S tímto vědomím přejděme k další části, abychom pochopili více o předávání argumentů do metody next().
Předávání argumentů do metody next()
S odkazem na výše uvedený blokový diagram si povíme o předávání argumentů do funkce next. Jedná se o jednu z nejsložitějších částí celé implementace generátoru.
Podívejme se na následující část kódu, kde je výnos přiřazen proměnné, ale tentokrát předáváme hodnotu v metodě next().
Podívejme se na níže uvedený kód v konzoli. A hned za ním vysvětlení.
Předávání argumentů metodě next()
Vysvětlení:
Když zavoláme první next(20), vypíše se každý řádek kódu až do prvního yield. Protože nemáme žádný předchozí výraz yield, je tato hodnota 20 zahozena. Na výstupu dostaneme hodnotu yield jako i*10, což je zde 100. Také stav provádění končí prvním yield a konst j ještě není nastavena.
Druhé volání next(10), nahradí celý první výraz yield hodnotou 10, představte si yield (i * 10) = 10, který pokračuje nastavením hodnoty konst j na 50 před vrácením hodnoty druhého yield. Hodnota yield je zde 2 * 50 / 4 = 25.
Třetí next(5), nahradí celý druhý yield hodnotou 5, čímž se hodnota k dostane na hodnotu 5. A dále pokračuje provedením příkazu return a vrátí (x + y + z) => (10 + 50 + 5) = 65 jako konečnou hodnotu yield spolu s done true.
Pro čtenáře, kteří to čtou poprvé, to může být trochu zdrcující, ale věnujte tomu dobrých 5 minut a přečtěte si to znovu a znovu, abyste to zcela pochopili.
Předávání yield jako argumentu funkce
V souvislosti s yield existuje n-tice případů použití, pokud jde o to, jak jej lze použít uvnitř generátoru funkcí. Podívejme se na níže uvedený kód na jedno takové zajímavé použití yield spolu s vysvětlením.
Výnos jako argument funkce
Vysvětlení
První funkce next() dává nedefinovanou hodnotu, protože výraz yield nemá hodnotu.
Druhá funkce next() dává hodnotu „I am usless“, která byla předána. A připraví argument pro volání funkce.
Třetí next(), volá funkci s nedefinovaným argumentem. Jak bylo uvedeno výše, metoda next() volaná bez argumentů v podstatě znamená, že celý předchozí výraz yield je nedefinovaný. Proto se vypíše nedefinováno a běh se ukončí.
Výnos s voláním funkce
Kromě vracení hodnot může yield také volat funkce a vracet hodnotu nebo ji vypisovat. Podívejme se na následující kód a lépe mu porozumíme.
Výnos s voláním funkce Výše uvedený kód vrací obj funkce jako hodnotu yield. A ukončí běh nastavením undefined na const user.
Výnos pomocí slibů
Výnos pomocí slibů používá stejný přístup jako výše uvedené volání funkce, místo aby vracel hodnotu z funkce, vrací slib, který může být dále vyhodnocen pro úspěch nebo neúspěch. Podívejme se na následující kód, abychom pochopili, jak to funguje.
Výnos se sliby Volání apiCall vrací sliby jako hodnotu yield, po vyřešení po 2 sekundách vypíše hodnotu, kterou potřebujeme.
Yield*
Dosud jsme se zabývali případy použití výrazu yield, nyní se podíváme na další výraz s názvem yield*. Výnos* při použití uvnitř generátorové funkce deleguje jinou generátorovou funkci. Zjednodušeně řečeno synchronně dokončí generátorovou funkci ve svém výrazu, než přejde na další řádek.
Podívejme se na kód a vysvětlení níže, abychom mu lépe porozuměli. Tento kód pochází z dokumentace na webu MDN.
Základní yield* Vysvětlení
První volání funkce next() dává hodnotu 1.
Druhé volání next() je však výraz yield*, což ve své podstatě znamená, že před pokračováním aktuální funkce generátoru dokončíme další funkci generátoru uvedenou ve výrazu yield*.
V myšlenkách můžete předpokládat, že výše uvedený kód je nahrazen podobně jako níže uvedený
function* g2() { yield 1; yield 2; yield 3; yield 4; yield 5; }
Ten bude pokračovat v dokončení běhu generátoru. Existuje však jedna výrazná schopnost yield*, kterou byste měli mít na paměti při používání return, v následující části:
Výnos* s návratem
Výnos* s návratem se chová trochu jinak než normální yield*. Když je yield* použit s příkazem return, vyhodnotí se na tuto hodnotu, což znamená, že celá funkce yield*() se stane rovna hodnotě vrácené z přidružené funkce generátoru.
Podívejme se na kód a vysvětlení níže, abychom jej lépe pochopili.
Yield* s return
Vysvětlení
V první funkci next() přejdeme rovnou na yield 1 a vrátíme její hodnotu.
V druhém next() se vrátí hodnota 2.
Třetí next(), vrátí ‚foo‘ a pokračuje k yield ‚the end‘, přičemž cestou přiřadí ‚foo‘ výsledku const.
Poslední next() ukončí běh.
Výnos* s vestavěným iterovatelným objektem
Za zmínku stojí ještě jedna zajímavá vlastnost yield*, podobně jako návratová hodnota umí yield* iterovat i nad iterovatelnými objekty jako Array, String a Map.
Podívejme se, jak to funguje v reálném čase.
Výnos nad vestavěnými iterovatelnými objekty
Tady v kódu yield* iteruje nad všemi možnými iterovatelnými objekty, které jsou předány jako jeho výraz. Myslím, že kód sám o sobě je srozumitelný.
Nejlepší postupy
Kromě toho všeho lze každý iterátor/generátor iterovat nad smyčkou for…of. Podobně jako naše metoda next(), která se volá explicitně, i smyčka for…of interně přechází na další iteraci na základě klíčového slova yield. A iteruje pouze do posledního yield a nezpracovává návratové příkazy jako metoda next().
To samé si můžete ověřit v následujícím kódu.
Výnos s for…of
Konečná návratová hodnota se nevypisuje, protože cyklus for…of iteruje pouze do posledního yield. V rámci osvědčených postupů tedy přichází v úvahu vyhnout se návratovým příkazům uvnitř generátorové funkce, protože by to ovlivnilo znovupoužitelnost funkce při iteraci přes for…of.
Závěr
Doufám, že jsem pokryl základní případy použití generátorových funkcí a pevně doufám, že jsem díky tomu lépe pochopil, jak generátory v JavaScriptu ES6 a vyšším fungují. Pokud se vám můj obsah líbí, zanechte prosím 1, 2, 3 nebo třeba 50 tlesknutí :).
Sledujte mě prosím na mém účtu GitHub, kde najdete další projekty v JavaScriptu a fullstacku: