JavaScript je asinkroni (neblokirajući) i jednonitni programski jezik, što znači da se samo jedan proces može pokrenuti u isto vrijeme.
U programskim jezicima, povratni pakao općenito se odnosi na neučinkovit način pisanja koda s asinkronim pozivima. Poznata je i kao Piramida propasti.
Pakao povratnog poziva u JavaScriptu se naziva situacija u kojoj se izvršava prevelika količina ugniježđenih funkcija povratnog poziva. Smanjuje čitljivost koda i održavanje. Situacija povratnog poziva obično se događa kada se radi o asinkronim operacijama zahtjeva, kao što je izrada višestrukih API zahtjeva ili rukovanje događajima sa složenim ovisnostima.
Da biste bolje razumjeli povratni poziv u JavaScriptu, prvo razumite povratne pozive i petlje događaja u JavaScriptu.
Povratni pozivi u JavaScriptu
JavaScript sve smatra objektom, kao što su nizovi, nizovi i funkcije. Stoga nam koncept povratnog poziva omogućuje prosljeđivanje funkcije kao argumenta drugoj funkciji. Funkcija povratnog poziva prvo će dovršiti izvršenje, a nadređena funkcija će se izvršiti kasnije.
Funkcije povratnog poziva izvode se asinkrono i dopuštaju da se kod nastavi izvoditi bez čekanja da dovrši asinkroni zadatak. Kada se kombinira više asinkronih zadataka, a svaki zadatak ovisi o prethodnom zadatku, struktura koda postaje komplicirana.
Hajdemo razumjeti upotrebu i važnost povratnih poziva. Pretpostavimo da primjer imamo funkciju koja uzima tri parametra, jedan niz i dva broja. Želimo neki izlaz na temelju teksta niza s više uvjeta.
Razmotrite primjer u nastavku:
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))
Izlaz:
30 20
Gornji kôd će dobro funkcionirati, ali moramo dodati još zadataka kako bismo kod učinili skalabilnim. Broj uvjetnih iskaza također će se povećavati, što će dovesti do neuredne strukture koda koju treba optimizirati i čitljiva.
Dakle, možemo prepisati kod na bolji način kako slijedi:
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))
Izlaz:
30 20
Ipak, rezultat će biti isti. Ali u gornjem primjeru, definirali smo njegovo zasebno tijelo funkcije i proslijedili funkciju kao funkciju povratnog poziva funkcijiexpectedResult. Stoga, ako želimo proširiti funkcionalnost očekivanih rezultata tako da možemo stvoriti drugo funkcionalno tijelo s drugom operacijom i koristiti ga kao funkciju povratnog poziva, to će olakšati razumijevanje i poboljšati čitljivost koda.
Postoje i drugi različiti primjeri povratnih poziva dostupnih u podržanim značajkama JavaScripta. Nekoliko uobičajenih primjera su slušatelji događaja i funkcije niza kao što su mapiranje, smanjivanje, filtriranje itd.
Da bismo to bolje razumjeli, trebali bismo razumjeti JavaScriptov prijenos po vrijednosti i prijenos po referenci.
JavaScript podržava dvije vrste tipova podataka koji su primitivni i neprimitivni. Primitivni tipovi podataka su nedefinirani, null, string i boolean, koji se ne mogu mijenjati, ili možemo reći usporedno nepromjenjivi; neprimitivni tipovi podataka su nizovi, funkcije i objekti koji se mogu mijenjati ili mijenjati.
Prijenos po referenci prosljeđuje referentnu adresu entiteta, kao što se funkcija može uzeti kao argument. Dakle, ako se promijeni vrijednost unutar te funkcije, to će promijeniti izvornu vrijednost, koja je dostupna izvan funkcije.
Usporedno, koncept prijenosa po vrijednosti ne mijenja svoju izvornu vrijednost, koja je dostupna izvan tijela funkcije. Umjesto toga, kopirat će vrijednost na dvije različite lokacije koristeći njihovu memoriju. JavaScript je identificirao sve objekte prema njihovoj referenci.
U JavaScriptu, addEventListener sluša događaje kao što su klik, prelazak mišem i izlaz miša i uzima drugi argument kao funkciju koja će se izvršiti nakon što se događaj pokrene. Ova se funkcija koristi konceptom prijenosa po referenci i prosljeđuje se korištenjem bez zagrada.
Razmotrite donji primjer; u ovom smo primjeru proslijedili funkciju greet kao argument u addEventListener kao funkciju povratnog poziva. Pozvat će se kada se pokrene događaj klika:
Test.html:
Javascript Callback Example <h3>Javascript Callback</h3> Click Here to Console const button = document.getElementById('btn'); const greet=()=>{ console.log('Hello, How are you?') } button.addEventListener('click', greet)
Izlaz:
U gornjem primjeru, proslijedili smo funkciju pozdrava kao argument u addEventListener kao funkciju povratnog poziva. Pozvat će se kada se pokrene događaj klika.
Slično tome, filtar je također primjer funkcije povratnog poziva. Ako koristimo filtar za ponavljanje niza, on će uzeti drugu funkciju povratnog poziva kao argument za obradu podataka niza. Razmotrite primjer u nastavku; u ovom primjeru koristimo funkciju larger za ispis broja većeg od 5 u nizu. Koristimo funkciju isGreater kao funkciju povratnog poziva u metodi filtra.
const arr = [3,10,6,7] const isGreater = num => num > 5 console.log(arr.filter(isGreater))
Izlaz:
[ 10, 6, 7 ]
Gornji primjer pokazuje da se funkcija larger koristi kao funkcija povratnog poziva u metodi filtra.
Da bismo bolje razumjeli povratne pozive, petlje događaja u JavaScriptu, razgovarajmo o sinkronom i asinkronom JavaScriptu:
Sinkroni JavaScript
Shvatimo koje su značajke sinkronog programskog jezika. Sinkrono programiranje ima sljedeće značajke:
Blokiranje izvršenja: Sinkroni programski jezik podržava tehniku blokiranja izvršenja što znači da blokira izvođenje sljedećih naredbi da će se izvršiti postojeće naredbe. Time se postiže predvidljivo i determinističko izvođenje naredbi.
Sekvencijalni tijek: Sinkrono programiranje podržava sekvencijalni tijek izvršenja, što znači da se svaka naredba izvršava sekvencijalno, kao jedna za drugom. Jezični program čeka da se naredba završi prije nego što prijeđe na sljedeću.
Jednostavnost: Često se smatra da je sinkrono programiranje lako razumljivo jer možemo predvidjeti njegov redoslijed tijeka izvršenja. Općenito, linearan je i lako ga je predvidjeti. Male aplikacije dobro je razvijati na ovim jezicima jer mogu podnijeti kritičan redoslijed operacija.
Izravno rukovanje pogreškama: U sinkronom programskom jeziku rukovanje pogreškama je vrlo jednostavno. Ako se pogreška dogodi kada se naredba izvršava, izbacit će pogrešku i program je može uhvatiti.
Ukratko, sinkrono programiranje ima dvije temeljne značajke, tj., jedan zadatak se izvršava u isto vrijeme, a sljedeći skup sljedećih zadataka bit će adresiran tek kada se trenutni zadatak završi. Pritom slijedi sekvencijalno izvođenje koda.
npm čisti predmemoriju
Ovakvo ponašanje programiranja kada se izvršava naredba, jezik stvara situaciju blok koda jer svaki posao mora čekati da se završi prethodni posao.
Ali kada ljudi govore o JavaScriptu, uvijek je bio zbunjujući odgovor je li sinkroni ili asinkroni.
U gore razmotrenim primjerima, kada smo koristili funkciju kao povratni poziv u funkciji filtra, ona je izvršena sinkrono. Stoga se naziva sinkrono izvršenje. Funkcija filtera mora čekati da veća funkcija završi svoje izvršenje.
Stoga se funkcija povratnog poziva naziva i blokirajući povratni poziv, budući da blokira izvršavanje nadređene funkcije u kojoj je pozvana.
Prvenstveno, JavaScript se smatra jednonitnim sinkronim i blokirajućim u prirodi. Ali koristeći nekoliko pristupa, možemo učiniti da radi asinkrono na temelju različitih scenarija.
Hajdemo sada razumjeti asinkroni JavaScript.
Asinkroni JavaScript
Asinkroni programski jezik usmjeren je na poboljšanje izvedbe aplikacije. Povratni pozivi mogu se koristiti u takvim scenarijima. Možemo analizirati asinkrono ponašanje JavaScripta pomoću primjera u nastavku:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000)
Iz gornjeg primjera, funkcija setTimeout uzima povratni poziv i vrijeme u milisekundama kao argumente. Povratni poziv se poziva nakon navedenog vremena (ovdje 1s). Ukratko, funkcija će čekati 1s za svoje izvršenje. Sada pogledajte kod u nastavku:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000) console.log('first') console.log('Second')
Izlaz:
first Second greet after 1 second
Iz gornjeg koda, poruke dnevnika nakon setTimeout-a će se prvo izvršiti dok će mjerač vremena proći. Dakle, rezultat je jedna sekunda, a zatim pozdravna poruka nakon vremenskog intervala od 1 sekunde.
U JavaScriptu, setTimeout je asinkrona funkcija. Kad god pozovemo funkciju setTimeout, ona registrira funkciju povratnog poziva (greet u ovom slučaju) koja će se izvršiti nakon navedene odgode. Međutim, ne blokira izvršenje sljedećeg koda.
U gornjem primjeru, poruke dnevnika su sinkrone izjave koje se izvršavaju odmah. Ne ovise o funkciji setTimeout. Stoga izvršavaju i bilježe svoje odgovarajuće poruke u konzolu bez čekanja odgode navedene u setTimeout.
U međuvremenu, petlja događaja u JavaScriptu obrađuje asinkrone zadatke. U tom slučaju čeka da prođe navedena odgoda (1 sekunda), a nakon što to vrijeme istekne, preuzima funkciju povratnog poziva (greet) i izvršava je.
Stoga se drugi kod nakon funkcije setTimeout izvršavao dok je radio u pozadini. Ovo ponašanje omogućuje JavaScriptu da obavlja druge zadatke dok čeka da se asinkrona operacija završi.
Moramo razumjeti stog poziva i red čekanja za povratne pozive kako bismo rukovali asinkronim događajima u JavaScriptu.
Razmotrite sliku u nastavku:
Iz gornje slike, tipični JavaScript mehanizam sastoji se od hrpe memorije i hrpe poziva. Stog poziva izvršava sav kod bez čekanja kada se gurne na stog.
Memorija gomile odgovorna je za dodjelu memorije za objekte i funkcije tijekom izvođenja kad god su potrebne.
Sada se naši motori preglednika sastoje od nekoliko web API-ja kao što su DOM, setTimeout, konzola, dohvaćanje itd., a motor može pristupiti tim API-jima pomoću globalnog objekta prozora. U sljedećem koraku, neke petlje događaja igraju ulogu vratara koji odabire zahtjeve za funkcijom unutar reda povratnih poziva i gura ih u stog. Ove funkcije, kao što je setTimeout, zahtijevaju određeno vrijeme čekanja.
Sada se vratimo našem primjeru, funkciji setTimeout; kada se naiđe na funkciju, mjerač vremena se registrira u redu povratnih poziva. Nakon toga, ostatak koda se gura u stog poziva i izvršava se kada funkcija dosegne ograničenje vremena, istekne, a red povratnih poziva gura funkciju povratnih poziva, koja ima navedenu logiku i registrirana je u funkciji vremenskog ograničenja . Stoga će se izvršiti nakon navedenog vremena.
Scenariji povratnog poziva
Sada smo razgovarali o povratnim pozivima, sinkronim, asinkronim i drugim relevantnim temama za pakao povratnih poziva. Hajdemo shvatiti što je pakao povratnog poziva u JavaScriptu.
Situacija kada je višestruki povratni poziv ugniježđen poznat je kao pakao povratnog poziva jer njegov oblik koda izgleda poput piramide, koja se također naziva i 'piramida propasti'.
Pakao povratnog poziva otežava razumijevanje i održavanje koda. Ovu situaciju uglavnom možemo vidjeti dok radimo u čvoru JS. Na primjer, razmotrite sljedeći primjer:
getArticlesData(20, (articles) => { console.log('article lists', articles); getUserData(article.username, (name) => { console.log(name); getAddress(name, (item) => { console.log(item); //This goes on and on... } })
U gornjem primjeru getUserData uzima korisničko ime koje ovisi o popisu članaka ili treba ekstrahirati getArticles odgovor koji se nalazi unutar članka. getAddress također ima sličnu ovisnost, koja ovisi o odgovoru getUserData. Ova situacija se zove pakao povratnog poziva.
Interni rad pakla povratnog poziva može se razumjeti pomoću primjera u nastavku:
Shvatimo da trebamo izvršiti zadatak A. Da bismo izvršili zadatak, potrebni su nam neki podaci iz zadatka B. Slično; imamo različite zadatke koji ovise jedni o drugima i izvršavaju se asinkrono. Stoga stvara niz funkcija povratnog poziva.
Hajdemo razumjeti obećanja u JavaScriptu i kako stvaraju asinkrone operacije, što nam omogućuje da izbjegnemo pisanje ugniježđenih povratnih poziva.
JavaScript obećava
U JavaScriptu, obećanja su uvedena u ES6. To je objekt sa sintaktičkim premazom. Zbog svog asinkronog ponašanja to je alternativni način izbjegavanja pisanja povratnih poziva za asinkrone operacije. Danas se web API-ji poput fetch() implementiraju korištenjem obećavajućeg, koji pruža učinkovit način pristupa podacima s poslužitelja. Također je poboljšao čitljivost koda i način da se izbjegne pisanje ugniježđenih povratnih poziva.
preslikavanje u strojopisu
Obećanja u stvarnom životu izražavaju povjerenje između dvije ili više osoba i jamstvo da će se određena stvar sigurno dogoditi. U JavaScriptu, Promise je objekt koji osigurava proizvodnju jedne vrijednosti u budućnosti (kada je to potrebno). Promise u JavaScriptu koristi se za upravljanje i rješavanje asinkronih operacija.
Promise vraća objekt koji osigurava i predstavlja završetak ili neuspjeh asinkronih operacija i njihov izlaz. To je zamjena za vrijednost bez poznavanja točnog izlaza. Korisno je za asinkrone radnje pružiti eventualnu vrijednost uspjeha ili razlog neuspjeha. Stoga asinkrone metode vraćaju vrijednosti poput sinkrone metode.
Općenito, obećanja imaju sljedeća tri stanja:
- Ispunjeno: Ispunjeno stanje je kada je primijenjena radnja uspješno razriješena ili dovršena.
- Na čekanju: stanje na čekanju je kada je zahtjev u obradi, a primijenjena radnja nije niti riješena niti odbijena te je još uvijek u početnom stanju.
- Odbijeno: Odbijeno stanje je kada je primijenjena radnja odbijena, uzrokujući neuspjeh željene operacije. Uzrok odbijanja može biti bilo što, uključujući neispravnost poslužitelja.
Sintaksa za obećanja:
let newPromise = new Promise(function(resolve, reject) { // asynchronous call is made //Resolve or reject the data });
Ispod je primjer pisanja obećanja:
Ovo je primjer pisanja obećanja.
function getArticleData(id) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Fetching data....'); resolve({ id: id, name: 'derik' }); }, 5000); }); } getArticleData('10').then(res=> console.log(res))
U gornjem primjeru možemo vidjeti kako možemo učinkovito koristiti obećanja za slanje zahtjeva s poslužitelja. Možemo primijetiti da je čitljivost koda veća u gornjem kodu nego u povratnim pozivima. Obećanja pružaju metode poput .then() i .catch(), koje nam omogućuju rukovanje statusom operacije u slučaju uspjeha ili neuspjeha. Možemo specificirati slučajeve za različita stanja obećanja.
Async/Await u JavaScriptu
To je još jedan način izbjegavanja upotrebe ugniježđenih povratnih poziva. Async/Await nam omogućuje mnogo učinkovitiju upotrebu obećanja. Možemo izbjeći korištenje lančanih metoda .then() ili .catch(). Ove metode također ovise o funkcijama povratnog poziva.
Async/Await se može precizno koristiti s Promiseom za poboljšanje performansi aplikacije. Interno je riješio obećanja i dao rezultat. Također, opet, čitljiviji je od metoda () ili catch().
Ne možemo koristiti Async/Await s normalnim funkcijama povratnog poziva. Da bismo ga koristili, moramo učiniti funkciju asinkronom tako da napišemo ključnu riječ async prije ključne riječi function. Međutim, interno također koristi ulančavanje.
Ispod je primjer 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('Error: ', err.message); } } displayData();
Da biste koristili Async/Await, funkcija mora biti određena ključnom riječi async, a ključna riječ await treba biti zapisana unutar funkcije. Async će zaustaviti svoje izvršenje dok se Promise ne riješi ili odbije. Nastavit će se kada se obećanje podijeli. Nakon što se riješi, vrijednost izraza čekanja bit će pohranjena u varijabli koja ga drži.
Sažetak:
Ukratko, možemo izbjeći ugniježđene povratne pozive korištenjem obećanja i async/await. Osim ovih, možemo slijediti i druge pristupe, kao što je pisanje komentara, a od pomoći može biti i dijeljenje koda na zasebne komponente. No, danas programeri preferiraju korištenje async/await.
Zaključak:
Pakao povratnog poziva u JavaScriptu se naziva situacija u kojoj se izvršava prevelika količina ugniježđenih funkcija povratnog poziva. Smanjuje čitljivost koda i održavanje. Situacija povratnog poziva obično se događa kada se radi o asinkronim operacijama zahtjeva, kao što je izrada višestrukih API zahtjeva ili rukovanje događajima sa složenim ovisnostima.
Da bismo bolje razumjeli pakao povratnog poziva u JavaScriptu.
JavaScript sve smatra objektom, kao što su nizovi, nizovi i funkcije. Stoga nam koncept povratnog poziva omogućuje prosljeđivanje funkcije kao argumenta drugoj funkciji. Funkcija povratnog poziva će prvo dovršiti izvršenje, a nadređena funkcija će se izvršiti kasnije.
Funkcije povratnog poziva izvode se asinkrono i dopuštaju da se kod nastavi izvoditi bez čekanja da dovrši asinkroni zadatak.