logo

Callback Hell v JavaScriptu

JavaScript je asynchronní (neblokovací) a jednovláknový programovací jazyk, což znamená, že v jednu chvíli lze spustit pouze jeden proces.

V programovacích jazycích se zpětným voláním peklo obecně rozumí neefektivní způsob psaní kódu pomocí asynchronních volání. Je také známá jako Pyramida zkázy.

Peklo zpětného volání v JavaScriptu je označováno jako situace, kdy se provádí nadměrné množství vnořených funkcí zpětného volání. Snižuje čitelnost kódu a údržbu. Situace pekla se zpětným voláním obvykle nastává, když se zabýváme operacemi asynchronních požadavků, jako je vytváření více požadavků API nebo zpracovávání událostí se složitými závislostmi.

Abyste lépe porozuměli peklu zpětných volání v JavaScriptu, nejprve porozumějte zpětným voláním a smyčkám událostí v JavaScriptu.

Zpětná volání v JavaScriptu

JavaScript považuje vše za objekt, jako jsou řetězce, pole a funkce. Koncept zpětného volání nám tedy umožňuje předat funkci jako argument jiné funkci. Funkce zpětného volání nejprve dokončí provádění a nadřazená funkce se provede později.

Funkce zpětného volání jsou prováděny asynchronně a umožňují kódu pokračovat v běhu bez čekání na dokončení asynchronní úlohy. Když se zkombinuje více asynchronních úloh a každá úloha závisí na předchozí úloze, struktura kódu se zkomplikuje.

Pojďme pochopit použití a důležitost zpětných volání. Předpokládejme například, že máme funkci, která má tři parametry, jeden řetězec a dvě čísla. Chceme nějaký výstup založený na řetězcovém textu s více podmínkami.

Zvažte níže uvedený příklad:

java tutoriál
 function expectedResult(action, x, y){ if(action === 'add'){ return x+y }else if(action === 'subtract'){ return x-y } } console.log(expectedResult('add',20,10)) console.log(expectedResult('subtract',30,10)) 

Výstup:

 30 20 

Výše uvedený kód bude fungovat dobře, ale musíme přidat další úkoly, aby byl kód škálovatelný. Počet podmíněných příkazů se bude také neustále zvyšovat, což povede k chaotické struktuře kódu, kterou je třeba optimalizovat a číst.

Můžeme tedy kód přepsat lepším způsobem takto:

 function add(x,y){ return x+y } function subtract(x,y){ return x-y } function expectedResult(callBack, x, y){ return callBack(x,y) } console.log(expectedResult(add, 20, 10)) console.log(expectedResult(subtract, 30, 10)) 

Výstup:

 30 20 

Přesto bude výstup stejný. Ale ve výše uvedeném příkladu jsme definovali její samostatné tělo funkce a předali funkci jako funkci zpětného volání do funkce expectResult. Pokud tedy chceme rozšířit funkčnost očekávaných výsledků, abychom mohli vytvořit další fungující tělo s jinou operací a použít jej jako funkci zpětného volání, usnadní to pochopení a zlepší čitelnost kódu.

Existují další různé příklady zpětných volání dostupných v podporovaných funkcích JavaScriptu. Několik běžných příkladů jsou posluchače událostí a funkce pole, jako je mapa, redukce, filtr atd.

Abychom tomu lépe porozuměli, měli bychom porozumět předávací hodnotě a předávané referenci JavaScriptu.

JavaScript podporuje dva typy datových typů, které jsou primitivní a neprimitivní. Primitivní datové typy jsou nedefinované, null, string a boolean, které nelze změnit, nebo můžeme říci, že jsou relativně neměnné; neprimitivní datové typy jsou pole, funkce a objekty, které lze měnit nebo měnit.

Pass by reference předává referenční adresu entity, podobně jako funkci lze brát jako argument. Pokud se tedy změní hodnota v rámci této funkce, změní se původní hodnota, která je k dispozici mimo funkci.

Ve srovnání s tím koncept pass-by-value nemění svou původní hodnotu, která je k dispozici mimo tělo funkce. Místo toho zkopíruje hodnotu do dvou různých umístění pomocí jejich paměti. JavaScript identifikoval všechny objekty podle jejich reference.

V JavaScriptu addEventListener naslouchá událostem, jako je kliknutí, přejetí myší a přejetí myší, a vezme druhý argument jako funkci, která se spustí po spuštění události. Tato funkce se používá konceptem pass by reference a předává se pomocí bez závorek.

Zvažte níže uvedený příklad; v tomto příkladu jsme předali funkci pozdravu jako argument do addEventListener jako funkci zpětného volání. Bude vyvolána při spuštění události kliknutí:

Test.html:

 Javascript Callback Example <h3>Javascript Callback</h3> Click Here to Console const button = document.getElementById(&apos;btn&apos;); const greet=()=&gt;{ console.log(&apos;Hello, How are you?&apos;) } button.addEventListener(&apos;click&apos;, greet) 

Výstup:

Callback Hell v JavaScriptu

Ve výše uvedeném příkladu jsme předali funkci pozdravu jako argument do addEventListener jako funkci zpětného volání. Bude vyvolána při spuštění události kliknutí.

Podobně je filtr také příkladem funkce zpětného volání. Pokud použijeme filtr k iteraci pole, bude to mít jinou funkci zpětného volání jako argument pro zpracování dat pole. Zvažte příklad níže; v tomto příkladu používáme funkci větší k vytištění čísla většího než 5 v poli. V metodě filtru používáme funkci isGreater jako funkci zpětného volání.

 const arr = [3,10,6,7] const isGreater = num =&gt; num &gt; 5 console.log(arr.filter(isGreater)) 

Výstup:

 [ 10, 6, 7 ] 

Výše uvedený příklad ukazuje, že větší funkce se používá jako funkce zpětného volání v metodě filtru.

Abychom lépe porozuměli zpětným voláním, cyklům událostí v JavaScriptu, pojďme diskutovat o synchronním a asynchronním JavaScriptu:

Synchronní JavaScript

Pojďme pochopit, jaké jsou vlastnosti synchronního programovacího jazyka. Synchronní programování má následující vlastnosti:

Blokování provedení: Synchronní programovací jazyk podporuje techniku ​​provádění blokování, což znamená, že blokuje provádění následujících příkazů, které budou provedeny stávající příkazy. Dosahuje tak předvídatelného a deterministického provádění příkazů.

Sekvenční tok: Synchronní programování podporuje sekvenční tok provádění, což znamená, že každý příkaz je vykonáván sekvenčním způsobem jako jeden po druhém. Jazykový program čeká na dokončení příkazu, než přejde k dalšímu.

Jednoduchost: Synchronní programování je často považováno za snadno srozumitelné, protože můžeme předvídat jeho pořadí toku provádění. Obecně je lineární a snadno předvídatelný. Malé aplikace je dobré vyvíjet v těchto jazycích, protože zvládnou kritické pořadí operací.

Přímé zpracování chyb: V synchronním programovacím jazyce je zpracování chyb velmi snadné. Pokud dojde k chybě při provádění příkazu, vyvolá chybu a program ji může zachytit.

Stručně řečeno, synchronní programování má dvě základní funkce, tj. jeden úkol se provádí najednou a další sada následujících úkolů bude řešena až po dokončení aktuálního úkolu. Tím následuje sekvenční provádění kódu.

Toto chování programování, když se provádí příkaz, jazyk vytváří situaci blokového kódu, protože každá úloha musí čekat na dokončení předchozí úlohy.

Ale když lidé mluví o JavaScriptu, vždy byla záhadná odpověď, zda je synchronní nebo asynchronní.

Ve výše diskutovaných příkladech, když jsme použili funkci jako zpětné volání ve funkci filtru, byla provedena synchronně. Proto se tomu říká synchronní provádění. Funkce filtru musí počkat, až větší funkce dokončí své provedení.

Proto se funkce zpětného volání také nazývá blokování zpětných volání, protože blokuje provedení nadřazené funkce, ve které byla vyvolána.

JavaScript je primárně považován za jednovláknový synchronní a svou povahou blokující. Ale pomocí několika přístupů můžeme zajistit, aby to fungovalo asynchronně na základě různých scénářů.

Pojďme nyní pochopit asynchronní JavaScript.

Asynchronní JavaScript

Asynchronní programovací jazyk se zaměřuje na zvýšení výkonu aplikace. V takových scénářích lze použít zpětná volání. Asynchronní chování JavaScriptu můžeme analyzovat na níže uvedeném příkladu:

 function greet(){ console.log(&apos;greet after 1 second&apos;) } setTimeout(greet, 1000) 

Z výše uvedeného příkladu bere funkce setTimeout jako argumenty zpětné volání a čas v milisekundách. Zpětné volání se vyvolá po uvedené době (zde 1s). Stručně řečeno, funkce počká na své provedení 1s. Nyní se podívejte na níže uvedený kód:

 function greet(){ console.log(&apos;greet after 1 second&apos;) } setTimeout(greet, 1000) console.log(&apos;first&apos;) console.log(&apos;Second&apos;) 

Výstup:

 first Second greet after 1 second 

Z výše uvedeného kódu budou zprávy protokolu po setTimeout provedeny jako první, zatímco časovač uplyne. Výsledkem je jedna sekunda a poté uvítací zpráva po 1 sekundovém intervalu.

V JavaScriptu je setTimeout asynchronní funkce. Kdykoli zavoláme funkci setTimeout, zaregistruje funkci zpětného volání (v tomto případě pozdrav), která má být provedena po zadaném zpoždění. Neblokuje však provedení následného kódu.

Ve výše uvedeném příkladu jsou zprávy protokolu synchronní příkazy, které se provádějí okamžitě. Nejsou závislé na funkci setTimeout. Proto spouštějí a zaznamenávají své příslušné zprávy do konzoly, aniž by čekali na zpoždění zadané v setTimeout.

Mezitím smyčka událostí v JavaScriptu zpracovává asynchronní úlohy. V tomto případě čeká, než uplyne zadaná prodleva (1 sekunda), a po uplynutí této doby vyzvedne funkci zpětného volání (pozdravit) a provede ji.

Druhý kód po funkci setTimeout se tedy spouštěl na pozadí. Toto chování umožňuje JavaScriptu provádět jiné úkoly při čekání na dokončení asynchronní operace.

Abychom zvládli asynchronní události v JavaScriptu, musíme porozumět zásobníku volání a frontě zpětných volání.

Zvažte následující obrázek:

Callback Hell v JavaScriptu

Z výše uvedeného obrázku vyplývá, že typický JavaScript engine se skládá z paměti haldy a zásobníku volání. Zásobník volání provede veškerý kód bez čekání, když je zatlačen do zásobníku.

Paměť haldy je zodpovědná za přidělování paměti pro objekty a funkce za běhu, kdykoli jsou potřeba.

Nyní se naše motory prohlížeče skládají z několika webových rozhraní API, jako je DOM, setTimeout, konzola, načítání atd., a motor může k těmto rozhraním API přistupovat pomocí objektu globálního okna. V dalším kroku hrají některé smyčky událostí roli správce brány, který vybírá požadavky na funkce uvnitř fronty zpětného volání a vkládá je do zásobníku. Tyto funkce, jako je setTimeout, vyžadují určitou dobu čekání.

Nyní se vraťme k našemu příkladu, funkci setTimeout; když se funkce objeví, časovač se zaregistruje do fronty zpětného volání. Poté je zbytek kódu vložen do zásobníku volání a bude proveden, jakmile funkce dosáhne limitu časovače, vyprší její platnost a fronta zpětného volání odešle funkci zpětného volání, která má zadanou logiku a je registrována ve funkci časového limitu. . Bude tedy spuštěn po zadané době.

Scénáře zpětného volání

Nyní jsme probrali zpětná volání, synchronní, asynchronní a další relevantní téma pro peklo zpětného volání. Pojďme pochopit, co je peklo zpětného volání v JavaScriptu.

Situace, kdy je vnořeno více zpětných volání, se nazývá peklo zpětného volání, protože tvar jeho kódu vypadá jako pyramida, které se také říká „pyramida zkázy“.

Peklo zpětného volání znesnadňuje pochopení a údržbu kódu. Tuto situaci můžeme většinou vidět při práci v node JS. Zvažte například následující příklad:

 getArticlesData(20, (articles) =&gt; { console.log(&apos;article lists&apos;, articles); getUserData(article.username, (name) =&gt; { console.log(name); getAddress(name, (item) =&gt; { console.log(item); //This goes on and on... } }) 

Ve výše uvedeném příkladu má getUserData uživatelské jméno, které závisí na seznamu článků nebo je třeba ho extrahovat getArticles odpověď, která je uvnitř článku. getAddress má také podobnou závislost, která je závislá na odpovědi getUserData. Této situaci se říká peklo zpětného volání.

Vnitřní fungování pekla zpětného volání lze pochopit na níže uvedeném příkladu:

Pochopme, že potřebujeme provést úlohu A. K provedení úlohy potřebujeme nějaká data z úlohy B.Podobně; máme různé úkoly, které jsou na sobě závislé a provádějí se asynchronně. Vytváří tak řadu funkcí zpětného volání.

Pojďme pochopit Promises v JavaScriptu a jak vytvářejí asynchronní operace, což nám umožňuje vyhnout se psaní vnořených zpětných volání.

JavaScript slibuje

V JavaScriptu byly sliby představeny v ES6. Jedná se o objekt se syntaktickým povlakem. Vzhledem ke svému asynchronnímu chování je to alternativní způsob, jak se vyhnout psaní zpětných volání pro asynchronní operace. V dnešní době jsou webová rozhraní API jako fetch() implementována pomocí slibných, což poskytuje efektivní způsob přístupu k datům ze serveru. Také zlepšila čitelnost kódu a je to způsob, jak se vyhnout psaní vnořených zpětných volání.

Sliby v reálném životě vyjadřují důvěru mezi dvěma nebo více osobami a ujištění, že se určitá věc jistě stane. V JavaScriptu je Promise objekt, který zajišťuje výrobu jediné hodnoty v budoucnu (když bude požadována). Promise v JavaScriptu se používá pro správu a řešení asynchronních operací.

Promise vrací objekt, který zajišťuje a představuje dokončení nebo selhání asynchronních operací a jejich výstup. Je to proxy pro hodnotu bez znalosti přesného výstupu. Pro asynchronní akce je užitečné poskytnout případnou hodnotu úspěchu nebo důvod selhání. Asynchronní metody tedy vracejí hodnoty jako synchronní metoda.

Obecně mají sliby následující tři stavy:

  • Splněno: Stav splněno je, když byla aplikovaná akce vyřešena nebo úspěšně dokončena.
  • Nevyřízeno: Stav Nevyřízeno je stav, kdy se požadavek zpracovává a použitá akce nebyla vyřešena ani zamítnuta a je stále ve svém počátečním stavu.
  • Odmítnuto: Stav odmítnutí je stav, kdy byla aplikovaná akce odmítnuta, což způsobilo selhání požadované operace. Příčinou odmítnutí může být cokoliv, včetně výpadku serveru.

Syntaxe slibů:

 let newPromise = new Promise(function(resolve, reject) { // asynchronous call is made //Resolve or reject the data }); 

Níže je uveden příklad psaní slibů:

Toto je příklad sepsání slibu.

 function getArticleData(id) { return new Promise((resolve, reject) =&gt; { setTimeout(() =&gt; { console.log(&apos;Fetching data....&apos;); resolve({ id: id, name: &apos;derik&apos; }); }, 5000); }); } getArticleData(&apos;10&apos;).then(res=&gt; console.log(res)) 

Ve výše uvedeném příkladu vidíme, jak můžeme efektivně využít přísliby k vytvoření požadavku ze serveru. Můžeme pozorovat, že čitelnost kódu je zvýšena ve výše uvedeném kódu než ve zpětných voláních. Promises poskytují metody jako .then() a .catch(), které nám umožňují zpracovat stav operace v případě úspěchu nebo selhání. Můžeme specifikovat případy pro různé stavy příslibů.

Async/Await v JavaScriptu

Je to další způsob, jak se vyhnout použití vnořených zpětných volání. Async/ Await nám umožňuje využívat sliby mnohem efektivněji. Můžeme se vyhnout použití řetězení metod .then() nebo .catch(). Tyto metody jsou také závislé na funkcích zpětného volání.

Async/Await lze přesně použít s Promise ke zlepšení výkonu aplikace. Interně vyřešilo sliby a poskytlo výsledek. Také je opět čitelnější než metody () nebo catch().

Nemůžeme použít Async/Await s normálními funkcemi zpětného volání. Abychom ji mohli použít, musíme funkci učinit asynchronní tak, že před klíčové slovo function napíšeme klíčové slovo async. Interně však také využívá řetězení.

Níže je uveden příklad Async/Await:

 async function displayData() { try { const articleData = await getArticle(10); const placeData = await getPlaces(article.name); const cityData = await getCity(place) console.log(city); } catch (err) { console.log(&apos;Error: &apos;, err.message); } } displayData(); 

Chcete-li použít Async/Await, funkce musí být specifikována pomocí klíčového slova async a klíčové slovo wait by mělo být zapsáno uvnitř funkce. Async zastaví své provádění, dokud není příslib vyřešen nebo odmítnut. Bude obnoveno, jakmile bude příslib rozdán. Po vyřešení bude hodnota výrazu wait uložena do proměnné, která jej obsahuje.

Souhrn:

Stručně řečeno, můžeme se vyhnout vnořeným zpětným voláním pomocí slibů a async/wait. Kromě toho můžeme sledovat i další přístupy, jako je psaní komentářů a užitečné může být i rozdělení kódu do samostatných komponent. Ale v dnešní době vývojáři preferují použití async/await.

Závěr:

Peklo zpětného volání v JavaScriptu je označováno jako situace, kdy se provádí nadměrné množství vnořených funkcí zpětného volání. Snižuje čitelnost kódu a údržbu. Situace pekla se zpětným voláním obvykle nastává, když se zabýváme operacemi asynchronních požadavků, jako je vytváření více požadavků API nebo zpracovávání událostí se složitými závislostmi.

Abychom lépe pochopili peklo zpětného volání v JavaScriptu.

JavaScript považuje vše za objekt, jako jsou řetězce, pole a funkce. Koncept zpětného volání nám tedy umožňuje předat funkci jako argument jiné funkci. Funkce zpětného volání nejprve dokončí provádění a nadřazená funkce se provede později.

Funkce zpětného volání jsou prováděny asynchronně a umožňují kódu pokračovat v běhu bez čekání na dokončení asynchronní úlohy.