Ak s programovaním iba začínate, je pravdepodobné, že o programoch uvažujete ako o množine sekvenčných blokov logiky, kde každý blok robí špecifickú vec a odovzdáva svoj výsledok, aby mohol bežať ďalší blok atď. väčšinou máte pravdu, väčšina programov beží sekvenčným spôsobom, tento model nám umožňuje vytvárať programy, ktoré sa jednoducho píšu a udržiavajú. Existujú však špecifické prípady použitia, kedy by tento sekvenčný model nefungoval alebo by nebol optimálny. Ako príklad si predstavte aplikáciu na čítanie kníh. Táto aplikácia má niekoľko pokročilých funkcií, ako je vyhľadávanie všetkých výskytov slova, navigácia medzi záložkami a podobne. Teraz si predstavte, že používateľ práve číta dlhú knihu a rozhodne sa vyhľadať všetky výskyty bežného slova, ako napríklad „The“. Aplikácii zvyčajne trvá niekoľko sekúnd, kým nájde a zaindexuje všetky výskyty tohto slova. V sekvenčnom programe používateľ nemôže interagovať s aplikáciou (zmeniť stránku alebo zvýrazniť text), kým sa nesplní operácia vyhľadávania. Dúfajme, že vidíte, že to nie je optimálna používateľská skúsenosť!
Diagram znázorňuje typický priebeh vykonávania aplikácie na čítanie kníh. Ak používateľ spustí dlhotrvajúcu operáciu (v tomto prípade vyhľadávanie všetkých výskytov „the“ vo veľkej knihe), aplikácia „zamrzne“ na celú dobu trvania tejto operácie. V tomto prípade bude používateľ klikať na ďalšie tlačidlo záložky bez výsledku, kým sa operácia vyhľadávania neskončí a všetky operácie sa prejavia naraz, čo dáva koncovému používateľovi pocit oneskorenej aplikácie.
Možno ste si všimli, že tento príklad v skutočnosti nezodpovedá sekvenčnému modelu, ktorý sme predstavili skôr. Je to preto, že operácie sú tu navzájom nezávislé. Používateľ nemusí vedieť o počte výskytov „the“, aby mohol prejsť na ďalšiu záložku, takže poradie vykonávania operácií nie je naozaj dôležité. Pred prechodom na ďalšiu záložku nemusíme čakať na koniec operácie vyhľadávania. Možné vylepšenie predchádzajúceho vykonávacieho toku je založené na tejto logike: môžeme spustiť operáciu dlhého vyhľadávania na pozadí, pokračovať s akýmikoľvek prichádzajúcimi operáciami a po vykonaní dlhej operácie môžeme jednoducho upozorniť používateľa. Priebeh vykonávania bude nasledujúci:
Vďaka tomuto toku vykonávania sa používateľská skúsenosť výrazne zlepší. Teraz môže používateľ spustiť dlhotrvajúcu operáciu, pokračovať v normálnom používaní aplikácie a po dokončení operácie dostane upozornenie. Toto je základ asynchrónneho programovania.
Javascript, okrem iných jazykov, podporuje tento štýl asynchrónneho programovania poskytovaním rozsiahlych rozhraní API na dosiahnutie takmer akéhokoľvek asynchrónneho správania, na ktoré si spomeniete. Na konci dňa by mal byť Javascript vo svojej podstate asynchrónnym jazykom. Ak sa odvoláme na predchádzajúci príklad, asynchrónna logika je základom všetkých aplikácií na interakciu s používateľmi a Javascript bol primárne vytvorený na použitie v prehliadači, kde väčšina programov reaguje na akcie používateľa.
Nasleduje stručný návod na asynchrónny Javascript:
Spätné volania
V typickom programe zvyčajne nájdete množstvo funkcií. Ak chcete použiť funkciu, voláme ju s množinou parametrov. Kód funkcie sa vykoná a vráti výsledok, nič neobvyklé. Asynchrónne programovanie túto logiku mierne posúva. Ak sa vrátime k príkladu aplikácie na čítanie kníh, nemôžeme použiť bežnú funkciu na implementáciu logiky operácie vyhľadávania, pretože operácia trvá neznáme množstvo času. Bežná funkcia sa v podstate vráti pred vykonaním operácie a toto nie je správanie, ktoré očakávame. Riešením je špecifikovať inú funkciu, ktorá sa vykoná po dokončení operácie vyhľadávania. Toto modeluje náš prípad použitia, pretože náš program môže normálne pokračovať vo svojom toku a po dokončení operácie vyhľadávania sa spustí špecifikovaná funkcia, ktorá používateľa upozorní na výsledky vyhľadávania. Túto funkciu nazývame funkcia spätného volania:
// Search occurrences function
function searchOccurrences(word, callback) {
try {
// search operation logic, result is in result variable
//....
callback(null, word, result);
} catch (err) {
callback(err);
}
}
// Search occurrences callback function
function handleSearchOccurrencesResult(err, word, result) {
if (err) {
console.log(`Search operation for ${word} ended with an error`);
} else console.log(`Search results for ${word}: ${result}`);
return;
}
searchOccurrences("the", handleSearchOccurrencesResult);
Najprv definujeme funkciu operácie vyhľadávania, searchOccurrences. Vyžaduje sa slovo na vyhľadanie a druhý parameter „spätné volanie“, ktorý bude funkciou, ktorá sa vykoná po dokončení operácie vyhľadávania. Funkcia operácie vyhľadávania bola zámerne abstraktná, musíme sa zamerať len na jej dva možné výsledky: prvý prípad je, keď všetko prebehlo úspešne a výsledok vyhľadávania máme v premennej result. V tomto prípade stačí zavolať funkciu spätného volania s nasledujúcimi parametrami: prvý parameter je null, čo znamená, že nenastala žiadna chyba, druhý parameter je hľadané slovo a tretí a možno najdôležitejší parameter z troch, je výsledkom pátracej akcie.
Druhým prípadom je, že dôjde k chybe, to je tiež prípad, keď je vykonaná operácia vyhľadávania a musíme zavolať funkciu spätného volania. Na zachytenie akejkoľvek chyby používame blok try and catch a zavoláme funkciu spätného volania s objektom chyby z bloku catch.
Potom sme definovali funkciu spätného volania handleSearchOccurrences, pričom sme zachovali jej logiku celkom jednoduchú. Ide len o vytlačenie správy do konzoly. Najprv skontrolujeme parameter „err“, aby sme zistili, či sa nevyskytla chyba v hlavnej funkcii. V takom prípade iba oznámime používateľovi, že operácia vyhľadávania sa skončila chybou. Ak neboli zaznamenané žiadne chyby, vytlačíme správu s výsledkom operácie vyhľadávania.
Nakoniec zavoláme funkciu searchOccurrences slovom „the“. Funkcia teraz pobeží normálne bez blokovania hlavného programu a po dokončení vyhľadávania sa vykoná spätné volanie a my dostaneme správu o výsledku buď s výsledkom vyhľadávania alebo chybovou správou.
Tu je dôležité spomenúť, že máme prístup iba k výslednej premennej v rámci hlavnej funkcie a funkcie spätného volania. Ak skúsime niečo takéto:
let result;
function searchOccurrences(word, callback) {
try {
// search operation logic, result is in searchResult variable
//....
result = searchResult;
callback(null, word, result);
} catch (err) {
callback(err);
}
}
searchOccurrences("the", handleSearchOccurrencesResult);
console.log(result);
výsledok tlače by bol nedefinovaný, pretože programy nečakajú na vykonanie funkcie searchOccurrences. Presunie sa na ďalšiu inštrukciu, ktorou je tlačový príkaz predtým, ako sa premenná result priradí do hlavnej funkcie. V dôsledku toho vytlačíme nepriradenú výslednú premennú.
Takže na základe tejto logiky by sme mali ponechať všetok kód, ktorý používa výslednú premennú, vo funkcii spätného volania. Teraz sa to nemusí zdať ako problém, ale môže to rýchlo prerásť do skutočného problému. Predstavte si prípad, keď máme reťaz asynchrónnych funkcií, ktoré musia bežať postupne. V typickej logike spätného volania by ste implementovali niečo takéto:
functionA(function (err, resA) {
///......
functionB(resA, function (err, resB) {
///......
functionC(resB, function (err, resC) {
///......
functionD(resC, function (err, resD) {
///......
});
});
});
});
Majte na pamäti, že každé spätné volanie má parameter chyby a každá chyba sa musí riešiť samostatne. To robí už aj tak zložitý kód vyššie ešte zložitejším a náročnejším na údržbu. Dúfajme, že Promises sú tu, aby vyriešili problém pekla so spätným volaním, tomu sa budeme venovať ďalej.
Sľuby
Sľuby sú postavené na spätných volaniach a fungujú podobným spôsobom. Boli predstavené ako súčasť funkcií ES6, aby vyriešili niekoľko do očí bijúcich problémov so spätnými volaniami, ako je napríklad peklo spätného volania. Sľuby poskytujú svoje vlastné funkcie, ktoré sa spúšťajú po úspešnom dokončení (vyriešení) a keď sa vyskytnú chyby (odmietnutie). Nasledujúci príklad zobrazuje príklad searchOccurrences implementovaný so sľubmi:
// Search occurrences function
function searchOccurrences(word) {
return new Promise((resolve, reject) => {
try {
// search operation logic, result is in result variable
//....
resolve(word, result);
} catch (err) {
reject(err);
}
});
}
searchOccurrences("the")
.then((word, result) => {
console.log(`Search results for ${word}: ${result}`);
})
.catch((err) => {
console.log(`Search operation ended with an error`);
});
Pozrime sa na zmeny, ktoré sme použili:
Funkcia searchOccurrences vráti prísľub. V rámci prísľubu zachovávame rovnakú logiku: máme dve funkcie vyriešiť a odmietnuť, ktoré predstavujú naše spätné volania, namiesto toho, aby sme mali jednu funkciu spätného volania, ktorá sa stará o úspešné vykonanie aj vykonanie s chybami. Sľuby oddeľujú dva výsledky a poskytujú čistú syntax pri volaní hlavnej funkcie. Funkcia rozlíšenia je „pripojená“ k hlavnej funkcii pomocou kľúčového slova „potom“. Tu len špecifikujeme dva parametre funkcie resolve a vytlačíme výsledok vyhľadávania. Podobná vec platí pre funkciu odmietnutia, možno ju pripojiť pomocou kľúčového slova „catch“. Dúfajme, že dokážete oceniť výhody, ktoré sľubujú, pokiaľ ide o čitateľnosť a čistotu kódu. Ak o tom stále diskutujete, pozrite sa, ako môžeme vyriešiť problém pekla so spätným volaním reťazením asynchrónnych funkcií tak, aby sa spúšťali jedna po druhej:
searchOccurrences("the")
.then(searchOccurrences("asynchronous"))
.then(searchOccurrences("javascript"))
.then(searchOccurrences("guide"))
.catch((err) => {
console.log(`Search operation ended with an error`);
});
Async/Await
Async/Await sú najnovším prírastkom do nášho asynchrónneho pásu nástrojov v jazyku Javascript. Predstavené s ES8, poskytujú novú vrstvu abstrakcie na vrchole asynchrónnych funkcií jednoduchým „čakaním“ na vykonanie asynchrónnej operácie. Tok programových blokov v tejto inštrukcii, kým sa nevráti výsledok z asynchrónnej operácie, a potom program bude pokračovať ďalšou inštrukciou. Ak uvažujete o synchrónnom toku vykonávania, máte pravdu. Uzatvorili sme kruh! Async/await sa pokúša priniesť jednoduchosť synchrónneho programovania do asynchrónneho sveta. Prosím, majte na pamäti, že to je vnímané len pri vykonávaní a kóde programu. Pod kapotou zostáva všetko po starom, Async/await stále používajú sľuby a ich stavebným kameňom sú spätné volania.
Prejdime si náš príklad a implementujeme ho pomocou Async/await:
async function searchOccurrences(word) {
try {
// search operation logic, result is in result variable
//....
return result;
} catch (err) {
console.log(`Search operation for ${word} ended with an error`);
}
}
const word = "the";
const result = await searchOccurrences(word, handleSearchOccurrencesResult);
console.log(`Search results for ${word}: ${result}`);
Náš kód sa príliš nezmenil, dôležité je tu si všimnúť kľúčové slovo „async“ pred deklaráciou funkcie searchOccurrences. To znamená, že funkcia je asynchrónna. Pri volaní funkcie searchOccurrences si tiež všimnite kľúčové slovo „wait“. To dá programu pokyn, aby počkal na vykonanie funkcie, kým sa nevráti výsledok, a potom sa program môže presunúť na ďalšiu inštrukciu, inými slovami, premenná result bude vždy obsahovať vrátenú hodnotu funkcie searchOccurrences a nie prísľub funkcia, v tomto zmysle, Async/Await nemá čakajúci stav ako Promises. Po vykonaní sa presunieme na tlačový príkaz a tentoraz výsledok skutočne obsahuje výsledok operácie vyhľadávania. Podľa očakávania má nový kód rovnaké správanie, ako keby bol synchrónny.
Ďalšia menšia vec, ktorú treba mať na pamäti, je, že keďže už nemáme funkcie spätného volania, musíme chybu searchOccurrences zvládnuť v rámci tej istej funkcie, pretože chybu nemôžeme len preniesť do funkcie spätného volania a tam ju spracovať. Tu len vypíšeme chybové hlásenie v prípade chyby pre príklad.
Zabaliť
V tomto článku sme prešli rôznymi prístupmi používanými na implementáciu asynchrónnej logiky v Javascripte. Začali sme skúmaním konkrétneho príkladu, prečo by sme potrebovali prejsť z bežného synchrónneho štýlu programovania na asynchrónny model. Potom sme prešli na spätné volania, ktoré sú hlavnými stavebnými kameňmi asynchrónneho Javascriptu. Obmedzenia spätných volaní nás viedli k rôznym alternatívam, ktoré boli v priebehu rokov pridané na prekonanie týchto obmedzení, najmä sľuby a async/čakanie. Asynchrónnu logiku možno nájsť kdekoľvek na webe, či už voláte externé API, iniciujete databázový dotaz, zapisujete do lokálneho súborového systému alebo dokonca čakáte na vstup používateľa do prihlasovacieho formulára. Dúfajme, že teraz máte väčšiu istotu pri riešení týchto problémov napísaním čistého a udržiavateľného asynchrónneho Javascriptu!
Ak sa vám tento článok páči, pozrite si Blog CLA, kde diskutujeme o rôznych témach, ako sa dostať do techniky. Pozrite si tiež náš youtube kanál, kde nájdete naše predchádzajúce bezplatné workshopy a sledujte nás na sociálnych médiách, aby ste neprišli o nadchádzajúce!