Průvodce asynchronním JavaScriptem pro začátečníky

javascript
promises
AsyncAwait
Průvodce asynchronním JavaScriptem pro začátečníky cover image

Pokud s programováním teprve začínáte, je pravděpodobné, že přemýšlíte o programech jako o sadě sekvenčních bloků logiky, kde každý blok dělá určitou věc a předává svůj výsledek, aby mohl běžet další blok a tak dále. z velké části máte pravdu, většina programů běží sekvenčním způsobem, tento model nám umožňuje vytvářet programy, které lze snadno psát a udržovat. Existují však specifické případy použití, kdy by tento sekvenční model nefungoval nebo by nebyl optimální. Jako příklad zvažte aplikaci pro čtení knih. Tato aplikace má několik pokročilých funkcí, jako je vyhledávání všech výskytů slova, navigace mezi záložkami a podobně. Nyní si představte, že uživatel právě čte dlouhou knihu a rozhodl se vyhledat všechny výskyty běžného slova, jako je „The“. Aplikace obvykle zabere několik sekund, než najde a zaindexuje všechny výskyty tohoto slova. V sekvenčním programu nemůže uživatel pracovat s aplikací (změnit stránku nebo zvýraznit text), dokud není operace vyhledávání dokončena. Doufejme, že vidíte, že to není optimální uživatelská zkušenost!

1

Diagram znázorňuje typický průběh provádění aplikace pro čtení knih. Pokud uživatel zahájí dlouhotrvající operaci (v tomto případě hledání všech výskytů „the“ ve velké knize), aplikace „zamrzne“ po celou dobu trvání této operace. V tomto případě bude uživatel klikat na další tlačítko záložky bez výsledku, dokud nebude operace vyhledávání dokončena a všechny operace se projeví najednou, což koncovému uživateli poskytne pocit zaostávající aplikace.

Možná jste si všimli, že tento příklad ve skutečnosti neodpovídá sekvenčnímu modelu, který jsme představili dříve. Je to proto, že operace jsou zde na sobě nezávislé. Aby uživatel mohl přejít na další záložku, nemusí vědět o počtu výskytů „the“, takže pořadí provádění operací není ve skutečnosti důležité. Než budeme moci přejít na další záložku, nemusíme čekat na konec operace vyhledávání. Možné vylepšení předchozího toku provádění je založeno na této logice: můžeme spustit operaci dlouhého vyhledávání na pozadí, pokračovat s libovolnými příchozími operacemi a jakmile je dlouhá operace hotová, můžeme jednoduše upozornit uživatele. Průběh provádění bude následující:

2

S tímto procesem provádění se uživatelská zkušenost výrazně zlepšuje. Nyní může uživatel zahájit dlouhotrvající operaci, pokračovat v běžném používání aplikace a dostat upozornění, jakmile je operace dokončena. To je základ asynchronního programování.

Javascript, mezi jinými jazyky, podporuje tento styl asynchronního programování tím, že poskytuje rozsáhlá API pro dosažení téměř jakéhokoli asynchronního chování, na které si vzpomenete. Na konci dne by měl být Javascript ze své podstaty asynchronním jazykem. Pokud se odkážeme na předchozí příklad, asynchronní logika je základem všech aplikací pro interakci s uživatelem a Javascript byl primárně vytvořen pro použití v prohlížeči, kde většina programů reaguje na akce uživatele.

Následující text vám poskytne stručného průvodce asynchronním Javascriptem:

Zpětná volání

V typickém programu obvykle najdete řadu funkcí. Chcete-li použít funkci, voláme ji se sadou parametrů. Funkční kód se spustí a vrátí výsledek, nic neobvyklého. Asynchronní programování tuto logiku mírně posouvá. Vrátíme-li se zpět k příkladu aplikace pro čtení knih, nemůžeme použít běžnou funkci k implementaci logiky vyhledávací operace, protože operace trvá neznámou dobu. Běžná funkce se v podstatě vrátí před provedením operace, a to není chování, které očekáváme. Řešením je zadat jinou funkci, která bude provedena po dokončení operace vyhledávání. To modeluje náš případ použití, protože náš program může normálně pokračovat ve svém toku a jakmile je vyhledávací operace dokončena, provede se zadaná funkce, která uživatele informuje o výsledcích vyhledávání. Tato funkce je to, co nazýváme funkcí zpětného volání:

// 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);

Nejprve definujeme funkci vyhledávací operace, searchOccurrences. Vyžaduje slovo k vyhledání a druhý parametr „zpětné volání“, což bude funkce, která se provede po dokončení operace vyhledávání. Funkce vyhledávací operace byla záměrně ponechána abstraktní, musíme se zaměřit pouze na její dva možné výsledky: první případ je, kdy vše proběhlo úspěšně a výsledek vyhledávání máme v proměnné result. V tomto případě stačí zavolat funkci zpětného volání s následujícími parametry: první parametr je null, což znamená, že nenastala žádná chyba, druhý parametr je hledané slovo a třetí a možná nejdůležitější parametr ze tří, je výsledkem pátrací operace.

Druhým případem je, kdy dojde k chybě, to je také případ, kdy je vykonána vyhledávací operace a musíme zavolat funkci zpětného volání. K zachycení jakékoli chyby používáme blok try and catch a pouze zavoláme funkci zpětného volání s objektem error z bloku catch.

Poté jsme definovali funkci zpětného volání handleSearchOccurrences, její logiku jsme zachovali poměrně jednoduchou. Jde pouze o vytištění zprávy na konzoli. Nejprve zkontrolujeme parametr „err“, abychom zjistili, zda nedošlo k nějaké chybě v hlavní funkci. V takovém případě pouze informujeme uživatele, že operace vyhledávání skončila s chybou. Pokud nebyly vyvolány žádné chyby, vytiskneme zprávu s výsledkem vyhledávací operace.

Nakonec zavoláme funkci searchOccurrences slovem „the“. Funkce nyní poběží normálně bez blokování hlavního programu a jakmile je vyhledávání dokončeno, provede se zpětné volání a my obdržíme zprávu o výsledku buď s výsledkem hledání, nebo chybovou zprávou.

Zde je důležité zmínit, že máme přístup pouze k výsledné proměnné uvnitř funkcí main a callback. Pokud zkusíme něco takového:

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ýsledek tisku by byl nedefinovaný, protože programy nečekají na provedení funkce searchOccurrences. Přesune se k další instrukci, která je příkazem print, než se do hlavní funkce přiřadí proměnná result. Výsledkem je tisk nepřiřazené výsledné proměnné.

Na základě této logiky bychom tedy měli ponechat veškerý kód, který používá proměnnou result, uvnitř funkce zpětného volání. Nyní se to nemusí zdát jako problém, ale může to rychle přerůst ve skutečný problém. Představte si případ, kdy máme řetězec asynchronních funkcí, které musí běžet v sekvenci, V typické logice zpětného volání byste implementovali něco takového:

functionA(function (err, resA) {
  ///......
  functionB(resA, function (err, resB) {
    ///......
    functionC(resB, function (err, resC) {
      ///......
      functionD(resC, function (err, resD) {
        ///......
      });
    });
  });
});

Mějte na paměti, že každé zpětné volání má parametr error a každá chyba musí být zpracována samostatně. Díky tomu je již tak složitý kód výše ještě složitější a obtížněji se udržuje. Doufejme, že Promises jsou tady, aby vyřešili problém se zpětným voláním, tomu se budeme věnovat příště.

Sliby

Sliby jsou postaveny na zpětných voláních a fungují podobným způsobem. Byly představeny jako součást funkcí ES6, aby vyřešily několik do očí bijících problémů se zpětnými voláními, jako je zpětné volání peklo. Promises poskytují své vlastní funkce, které běží po úspěšném dokončení (vyřešit) a když se vyskytnou chyby (odmítnout). Následující příklad ukazuje příklad searchOccurrences implementovaný se sliby:

// 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`);
  });

Pojďme se podívat na změny, které jsme použili:

Funkce searchOccurrences vrací příslib. Uvnitř slibu zachováváme stejnou logiku: máme dvě funkce vyřešit a odmítnout, které představují naše zpětná volání, spíše než mít jedinou funkci zpětného volání, která se stará o úspěšné provedení i provedení s chybami. Sliby oddělují dva výsledky a poskytují čistou syntaxi při volání hlavní funkce. Funkce vyřešení je „připojena“ k hlavní funkci pomocí klíčového slova „then“. Zde pouze specifikujeme dva parametry funkce resolve a vytiskneme výsledek hledání. Podobná věc platí pro funkci odmítnutí, lze ji zaháknout pomocí klíčového slova „catch“. Doufejme, že dokážete ocenit výhody, které slibují, pokud jde o čitelnost a čistotu kódu. Pokud o tom stále diskutujete, podívejte se, jak můžeme vyřešit problém pekla zpětného volání zřetězením asynchronních funkcí, aby se spouštěly jedna po druhé:

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 jsou nejnovějším přírůstkem do našeho asynchronního toolbeltu v Javascriptu. Představené s ES8 poskytují novou vrstvu abstrakce nad asynchronními funkcemi pouhým „čekáním“ na provedení asynchronní operace. Tok programových bloků v této instrukci, dokud není vrácen výsledek z asynchronní operace, a poté bude program pokračovat další instrukcí. Pokud přemýšlíte o toku synchronního provádění, máte pravdu. Uzavřeli jsme kruh! Async/await se pokouší přinést jednoduchost synchronního programování do asynchronního světa. Mějte prosím na paměti, že to je vnímáno pouze při provádění a kódu programu. Pod kapotou zůstává vše při starém, Async/await stále používají sliby a zpětná volání jsou jejich stavebními kameny.

Pojďme si projít náš příklad a implementovat jej pomocí 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 se příliš nezměnil, důležité je zde poznamenat klíčové slovo „async“ před deklarací funkce searchOccurrences. To znamená, že funkce je asynchronní. Při volání funkce searchOccurrences si také všimněte klíčového slova „wait“. To dá programu pokyn, aby počkal na provedení funkce, dokud nebude vrácen výsledek, než se program bude moci přesunout k další instrukci, jinými slovy, proměnná result bude vždy obsahovat vrácenou hodnotu funkce searchOccurrences a ne příslib funkce v tomto smyslu Async/Await nemá stav čekající na vyřízení jako Promises. Jakmile je provedení hotové, přejdeme k tiskovému příkazu a tentokrát výsledek skutečně obsahuje výsledek vyhledávací operace. Podle očekávání má nový kód stejné chování, jako kdyby byl synchronní.

Další drobná věc, kterou je třeba mít na paměti, je, že protože již nemáme funkce zpětného volání, musíme chybu searchOccurrences zpracovat uvnitř stejné funkce, protože chybu nemůžeme pouze přenést do funkce zpětného volání a zpracovat ji tam. Zde pouze tiskneme chybové hlášení pro případ chyby pro příklad.

Shrnutí

V tomto článku jsme prošli různými přístupy používanými k implementaci asynchronní logiky v Javascriptu. Začali jsme prozkoumáním konkrétního příkladu, proč bychom měli přejít od běžného synchronního stylu programování k asynchronnímu modelu. Poté jsme přešli na zpětná volání, což jsou hlavní stavební kameny asynchronního Javascriptu. Omezení zpětných volání nás vedla k různým alternativám, které byly během let přidány, aby tato omezení překonaly, hlavně sliby a Async/wait. Asynchronní logiku lze nalézt kdekoli na webu, ať už voláte externí API, spouštíte databázový dotaz, zapisujete do místního souborového systému nebo dokonce čekáte na vstup uživatele do přihlašovacího formuláře. Doufejme, že se nyní cítíte jistější při řešení těchto problémů psaním čistého a udržovatelného asynchronního Javascriptu!

Pokud se vám tento článek líbí, podívejte se prosím na Blog CLA, kde diskutujeme o různých tématech, jak se dostat do techniky. Podívejte se také na náš youtube kanál pro naše předchozí bezplatné workshopy a sledujte nás na sociálních médiích, abyste nepřišli o nadcházející!


Zajistěte budoucnost své kariéry zvýšením dovedností v HTML, CSS a JavaScript s Code Labs Academy Web Development Bootcamp.


Career Services background pattern

Kariérní služby

Contact Section background image

Zůstaňme v kontaktu

Code Labs Academy © 2024 Všechna práva vyhrazena.