Při vývoji aplikací v JavaScriptu se můžeme setkat s asynchronními operacemi, například při stahování dat v prohlížeči nebo při práci se soubory v prostředí Node.js.
Pokud bychom s těmito funkcemi pracovali jako s běžnými, synchronními, mohli bychom se dočkat nečekaných výsledků. Je to proto, že se jedná o asynchronní operace. Tento článek objasní, co to asynchronní operace jsou a jak s nimi efektivně pracovat.
Úvod do synchronních funkcí
JavaScript je jazyk s jedním vláknem, což znamená, že může vykonávat pouze jednu operaci v daný okamžik. Pokud procesor narazí na funkci, která trvá delší dobu, JavaScript musí počkat na její dokončení, než se může přesunout k dalšímu kódu.
Většina funkcí je vykonávána přímo procesorem. Během jejich provádění je procesor zcela zaneprázdněn, bez ohledu na to, jak dlouho to trvá. Takové funkce se nazývají synchronní. Následuje příklad:
function add(a, b) { for (let i = 0; i < 1000000; i ++) { // Nic se neděje } return a + b; } // Volání této funkce bude trvat sum = add(10, 5); // Procesor nemůže pokračovat k dalším instrukcím console.log(sum);
Tato funkce obsahuje cyklus, jehož provedení trvá delší dobu, než vrátí součet dvou vstupních parametrů.
Po definování funkce jsme ji zavolali a výsledek uložili do proměnné *sum*. Následně jsme vypsali hodnotu proměnné *sum*. I když provedení funkce *add* zabere nějaký čas, procesor nemůže přejít k výpisu hodnoty *sum* dokud není funkce *add* zcela dokončena.
Většina funkcí, se kterými se setkáte, se chová předvídatelně. Některé funkce jsou ale asynchronní a nechovají se jako ty běžné.
Úvod do asynchronních funkcí
Asynchronní funkce vykonávají většinu své práce mimo procesor. I když dokončení takové funkce může trvat delší dobu, procesor může být volný a vykonávat další operace.
Příklad takové funkce:
fetch('https://jsonplaceholder.typicode.com/users/1');
Pro zvýšení efektivity JavaScript umožňuje procesoru přesunout se k dalším úlohám, které vyžadují jeho výpočetní výkon, ještě před dokončením provádění asynchronní funkce.
Vzhledem k tomu, že procesor pokračuje dál, i když asynchronní funkce ještě nebyla dokončena, její výsledek není okamžitě k dispozici. Je potřeba počkat. Pokud by se procesor snažil provést další části programu, které na nedokončeném výsledku závisí, došlo by k chybám.
Proto by procesor měl provádět pouze části programu, které nejsou závislé na nedokončeném výsledku. K tomu se v moderním JavaScriptu používají „promises“ (sliby).
Co je to „promise“ v JavaScriptu?
„Promise“ (slib) v JavaScriptu je dočasná hodnota, kterou vrací asynchronní funkce. Sliby jsou základem moderního asynchronního programování v JavaScriptu.
Jakmile je slib vytvořen, může nastat jedna ze dvou událostí. Buď je „resolved“ (vyřešen) v případě, že je výsledek asynchronní funkce úspěšně vygenerován, nebo je „rejected“ (odmítnut) v případě chyby. Toto jsou dva stavy slibu. Ke slibu můžeme připojit obslužné rutiny, které se provedou, až se slib vyřeší nebo odmítne.
Veškerý kód, který vyžaduje výsledek asynchronní funkce, může být připojen k obslužné rutině slibu, který se spustí po jeho vyřešení. Stejně tak může být připojen kód, který zpracovává chybu v případě, že je slib odmítnut.
Následuje příklad čtení dat ze souboru v Node.js:
const fs = require('fs/promises'); fileReadPromise = fs.readFile('./hello.txt', 'utf-8'); fileReadPromise.then((data) => console.log(data)); fileReadPromise.catch((error) => console.log(error));
Na prvním řádku importujeme modul *fs/promises*.
Na druhém řádku voláme funkci *readFile*, které předáme jméno souboru a jeho kódování. Funkce vrací slib, který uložíme do proměnné *fileReadPromise*.
Na třetím řádku připojíme posluchač události, která nastane, když se slib vyřeší. Uděláme to voláním metody *then* na objektu slibu. Jako argument předáme funkci, která se má spustit, až se slib vyřeší.
Na čtvrtém řádku připojíme posluchač události, která nastane v případě, že je slib odmítnut. To se provede voláním metody *catch* a předáním obslužné rutiny jako argumentu.
Alternativou je použití klíčových slov *async* a *await*. Tomuto přístupu se budeme věnovat dále.
Vysvětlení *async* a *await*
Klíčová slova *async* a *await* umožňují psát asynchronní kód v JavaScriptu s čistším a srozumitelnějším syntaxem. V této části vysvětlím, jak tato klíčová slova používat a jaký mají vliv na váš kód.
Klíčové slovo *await* se používá k pozastavení vykonávání funkce při čekání na dokončení asynchronní operace. Následuje příklad:
const fs = require('fs/promises'); function readData() { const data = await fs.readFile('./hello.txt', 'utf-8'); // Tento řádek se neprovede, dokud nebudou data dostupná console.log(data); } readData()
Při volání funkce *readFile* jsme použili klíčové slovo *await*. Tím se procesoru dává pokyn, aby počkal na načtení souboru, než bude možné spustit následující řádek (*console.log*). To zajišťuje, že kód, který závisí na výsledku asynchronní operace, se neprovede, dokud nebude výsledek k dispozici.
Pokud byste se pokusili spustit výše uvedený kód, narazili byste na chybu. Klíčové slovo *await* se totiž smí používat pouze uvnitř funkce, která je definovaná jako asynchronní. Chcete-li funkci deklarovat jako asynchronní, použijte klíčové slovo *async* před její deklarací, takto:
const fs = require('fs/promises'); async function readData() { const data = await fs.readFile('./hello.txt', 'utf-8'); // Tento řádek se neprovede, dokud nebudou data dostupná console.log(data); } // Volání funkce pro její spuštění readData() // Tento kód se provede, zatímco se čeká na dokončení funkce readData console.log('Čekání na data')
Po spuštění tohoto úryvku kódu uvidíte, že JavaScript spustí vnější *console.log*, zatímco bude čekat na data načtená z textového souboru. Jakmile jsou data k dispozici, provede se *console.log* uvnitř funkce *readData*.
Ošetření chyb při použití klíčových slov *async* a *await* se obvykle provádí pomocí bloků try/catch. Důležité je také vědět, jak iterovat s asynchronním kódem.
Klíčová slova *async* a *await* jsou dostupné v moderním JavaScriptu. Tradičně se asynchronní kód psal pomocí takzvaných „callbacks“ (zpětných volání).
Úvod do zpětných volání
Zpětné volání je funkce, která se zavolá, jakmile je k dispozici výsledek operace. Veškerý kód, který vyžaduje výsledek operace, bude umístěn uvnitř této funkce. Vše ostatní mimo zpětné volání není na výsledku závislé, a lze to provést nezávisle.
Zde je příklad čtení souboru v Node.js.
const fs = require("fs"); fs.readFile("./hello.txt", "utf-8", (err, data) => { // V této funkci umisťujeme kód, který vyžaduje výsledek operace if (err) console.log(err); else console.log(data); }); // V této části kódu můžeme provést veškeré operace, které na výsledku čtení souboru nezávisí console.log("Ahoj z programu")
Na prvním řádku importujeme modul *fs*. Poté voláme funkci *readFile* modulu *fs*. Tato funkce načte text ze souboru. První argument udává, o jaký soubor se jedná, a druhý definuje jeho formát.
Funkce *readFile* čte text ze souboru asynchronně. K tomu potřebuje funkci jako argument. Tato funkce je takzvaná „callback“ a bude zavolána, jakmile budou data načtena.
První argument předaný funkci zpětného volání je chyba, která bude mít nějakou hodnotu v případě, že při operaci nastane. Pokud k chybě nedojde, bude *undefined*.
Druhý argument jsou data načtená ze souboru. Kód uvnitř této funkce bude mít přístup k těmto datům. Kód mimo tuto funkci tato data nepotřebuje, a tak může být proveden, zatímco se na data čeká.
Spuštění výše uvedeného kódu by dalo následující výstup:
Klíčové vlastnosti JavaScriptu
Existuje několik klíčových vlastností JavaScriptu, které mají vliv na to, jak funguje asynchronní kód. Jsou dobře vysvětleny ve videu níže:
Dále stručně shrnu dvě důležité vlastnosti.
#1. Jednovláknový
Na rozdíl od jiných jazyků, které umožňují programátorovi používat více vláken, JavaScript používá pouze jedno vlákno. Vlákno je posloupnost instrukcí, které na sobě logicky závisí. Více vláken umožňuje programu provádět jiné vlákno v případě, že dojde k blokující operaci.
Více vláken však zvyšuje složitost a ztěžuje pochopení programů, které je používají. Tím se zvyšuje pravděpodobnost výskytu chyb a ztěžuje se debugování. Z důvodu jednoduchosti byl JavaScript navržen jako jednovláknový jazyk. Aby se efektivně vypořádal s blokujícími operacemi, spoléhá se na zpracování událostí.
#2. Řízený událostmi
JavaScript je řízen událostmi. To znamená, že během životního cyklu programu JavaScript dochází k různým událostem. Jako programátor můžete k těmto událostem připojit funkce, a kdykoli k události dojde, bude připojená funkce spuštěna.
Některé události mohou nastat, když je k dispozici výsledek blokující operace. V takovém případě je s výsledkem zavolána přidružená funkce.
Co zvážit při psaní asynchronního JavaScriptu
V této poslední části zmíním několik věcí, které je třeba vzít v úvahu při psaní asynchronního JavaScriptu. Bude se jednat o podporu prohlížečů, osvědčené postupy a důležitost.
Podpora prohlížečů
Toto je tabulka zobrazující podporu slibů v různých prohlížečích.
Zdroj: caniuse.com
Toto je tabulka zobrazující podporu klíčových slov async v různých prohlížečích.
Zdroj: caniuse.com
Osvědčené postupy
- Vždy upřednostňujte *async/await*, protože vám pomůže psát čistší kód, o kterém se lépe přemýšlí.
- Ošetřujte chyby pomocí bloků *try/catch*.
- Klíčové slovo *async* používejte pouze tehdy, pokud je potřeba čekat na výsledek funkce.
Důležitost asynchronního kódu
Asynchronní kód vám umožňuje psát efektivnější programy, které využívají pouze jedno vlákno. To je důležité, protože JavaScript se používá k vytváření webových stránek, které provádějí mnoho asynchronních operací, jako jsou síťové požadavky nebo čtení/zápis dat na disk. Tato efektivita umožnila běhovým prostředím, jako je Node.js, stát se oblíbenou platformou pro aplikační servery.
Závěrem
Tento článek byl rozsáhlý, ale myslím, že se nám podařilo vysvětlit, jak se asynchronní funkce liší od synchronních. Podívali jsme se také na to, jak pracovat s asynchronním kódem pomocí slibů, klíčových slov *async/await* a zpětných volání.
Kromě toho jsme probrali klíčové vlastnosti jazyka JavaScript. V poslední části jsme se zaměřili na podporu prohlížečů a osvědčené postupy.
Dále se můžete podívat na často kladené otázky týkající se pohovorů o Node.js.