logo

Callback Hell în JavaScript

JavaScript este un limbaj de programare asincron (neblocant) și cu un singur thread, ceea ce înseamnă că numai un proces poate fi rulat la un moment dat.

În limbajele de programare, callback hell se referă în general la un mod ineficient de a scrie cod cu apeluri asincrone. Este cunoscută și sub numele de Pyramid of Doom.

Iadul de apel invers în JavaScript este denumit o situație în care se execută o cantitate excesivă de funcții de apel invers imbricate. Reduce lizibilitatea codului și întreținerea. Situația iadului de apel invers apare de obicei atunci când aveți de-a face cu operațiuni de solicitare asincrone, cum ar fi efectuarea de solicitări multiple API sau gestionarea evenimentelor cu dependențe complexe.

Pentru a înțelege mai bine iadul de apel invers în JavaScript, înțelegeți mai întâi apelurile și buclele de evenimente din JavaScript.

Reapeluri în JavaScript

JavaScript consideră totul ca un obiect, cum ar fi șiruri de caractere, matrice și funcții. Prin urmare, conceptul de apel invers ne permite să transmitem funcția ca argument unei alte funcții. Funcția de apel invers va finaliza mai întâi execuția, iar funcția părinte va fi executată mai târziu.

Funcțiile de apel invers sunt executate asincron și permit codului să continue să ruleze fără a aștepta finalizarea sarcinii asincrone. Când sunt combinate mai multe sarcini asincrone și fiecare sarcină depinde de sarcina sa anterioară, structura codului devine complicată.

Să înțelegem utilizarea și importanța apelurilor inverse. Să presupunem că, un exemplu, avem o funcție care ia trei parametri, un șir și două numere. Vrem o ieșire bazată pe textul șirului cu mai multe condiții.

Luați în considerare exemplul de mai jos:

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

Ieșire:

 30 20 

Codul de mai sus va funcționa bine, dar trebuie să adăugăm mai multe sarcini pentru a face codul scalabil. Numărul de instrucțiuni condiționate va continua să crească, ceea ce va duce la o structură de cod dezordonată care trebuie optimizată și lizibilă.

Deci, putem rescrie codul într-un mod mai bun, după cum urmează:

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

Ieșire:

 30 20 

Totuși, rezultatul va fi același. Dar, în exemplul de mai sus, am definit corpul său separat al funcției și am transmis funcția ca funcție de apel invers funcției waitResult. Prin urmare, dacă dorim să extindem funcționalitatea rezultatelor așteptate, astfel încât să putem crea un alt corp funcțional cu o operațiune diferită și să-l folosim ca funcție de apel invers, va face mai ușor de înțeles și de îmbunătățit lizibilitatea codului.

Există și alte exemple diferite de apeluri inverse disponibile în funcțiile JavaScript acceptate. Câteva exemple comune sunt ascultătorii de evenimente și funcțiile de matrice, cum ar fi maparea, reducerea, filtrarea etc.

Pentru a-l înțelege mai bine, ar trebui să înțelegem trecerea prin valoare și referințele JavaScript.

JavaScript acceptă două tipuri de tipuri de date, care sunt primitive și non-primitive. Tipurile de date primitive sunt nedefinite, nule, șir și boolean, care nu pot fi modificate sau putem spune comparativ imuabile; tipurile de date neprimitive sunt matrice, funcții și obiecte care pot fi modificate sau mutabile.

Pass by reference transmite adresa de referință a unei entități, așa cum o funcție poate fi luată ca argument. Deci, dacă valoarea din acea funcție este modificată, aceasta va modifica valoarea inițială, care este disponibilă în afara funcției.

Comparativ, conceptul de trecere prin valoare nu își schimbă valoarea inițială, care este disponibilă în afara corpului funcției. În schimb, va copia valoarea în două locații diferite folosind memoria lor. JavaScript a identificat toate obiectele prin referința lor.

În JavaScript, addEventListener ascultă evenimente precum click, mouseover și mouseout și ia al doilea argument ca funcție care va fi executată odată ce evenimentul este declanșat. Această funcție este utilizată prin conceptul de trecere prin referință și transmisă fără paranteză.

Luați în considerare exemplul de mai jos; în acest exemplu, am trecut o funcție greet ca argument în addEventListener ca funcție de apel invers. Acesta va fi invocat atunci când evenimentul clic este declanșat:

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) 

Ieșire:

Callback Hell în JavaScript

În exemplul de mai sus, am trecut o funcție de salut ca argument în addEventListener ca funcție de apel invers. Acesta va fi invocat atunci când evenimentul clic este declanșat.

În mod similar, filtrul este, de asemenea, un exemplu de funcție de apel invers. Dacă folosim un filtru pentru a repeta o matrice, va fi nevoie de o altă funcție de apel invers ca argument pentru a procesa datele matricei. Luați în considerare exemplul de mai jos; în acest exemplu, folosim funcția mai mare pentru a tipări numărul mai mare de 5 în matrice. Folosim funcția isGreater ca funcție de apel invers în metoda de filtrare.

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

Ieșire:

 [ 10, 6, 7 ] 

Exemplul de mai sus arată că funcția mai mare este utilizată ca funcție de apel invers în metoda de filtrare.

sortare rapida

Pentru a înțelege mai bine apelurile, buclele de evenimente în JavaScript, să discutăm despre JavaScript sincron și asincron:

JavaScript sincron

Să înțelegem care sunt caracteristicile unui limbaj de programare sincron. Programarea sincronă are următoarele caracteristici:

Blocarea execuției: Limbajul de programare sincronă suportă tehnica de execuție blocare, ceea ce înseamnă că blochează execuția instrucțiunilor ulterioare, instrucțiunile existente vor fi executate. Astfel, realizează execuția predictibilă și deterministă a instrucțiunilor.

Flux secvenţial: Programarea sincronă acceptă fluxul secvenţial de execuţie, ceea ce înseamnă că fiecare instrucţiune este executată într-un mod secvenţial, una după alta. Programul de limbă așteaptă finalizarea unei instrucțiuni înainte de a trece la următoarea.

Simplitate: Adesea, programarea sincronă este considerată la fel de ușor de înțeles deoarece îi putem prezice ordinea fluxului de execuție. În general, este liniar și ușor de prezis. Aplicațiile mici sunt bine dezvoltate pe aceste limbaje, deoarece pot gestiona ordinea critică a operațiunilor.

Tratarea directă a erorilor: Într-un limbaj de programare sincron, gestionarea erorilor este foarte ușoară. Dacă apare o eroare atunci când se execută o instrucțiune, aceasta va genera o eroare și programul o poate prinde.

Pe scurt, programarea sincronă are două caracteristici de bază, adică o singură sarcină este executată la un moment dat, iar următorul set de sarcini următoare va fi abordat numai după ce sarcina curentă este terminată. Prin urmare, urmează o execuție secvențială a codului.

Acest comportament al programării atunci când se execută o instrucțiune, limbajul creează o situație de cod de bloc, deoarece fiecare job trebuie să aștepte ca jobul anterior să fie finalizat.

Dar când oamenii vorbesc despre JavaScript, a fost întotdeauna un răspuns nedumerit, indiferent dacă este sincron sau asincron.

În exemplele discutate mai sus, când am folosit o funcție ca apel invers în funcția de filtru, aceasta a fost executată sincron. Prin urmare, se numește execuție sincronă. Funcția de filtru trebuie să aștepte ca funcția mai mare să își termine execuția.

Prin urmare, funcția de apel invers se mai numește și blocarea apelurilor inverse, deoarece blochează execuția funcției părinte în care a fost invocată.

În primul rând, JavaScript este considerat sincron cu un singur fir și blocant în natură. Dar folosind câteva abordări, îl putem face să funcționeze asincron pe baza diferitelor scenarii.

Acum, să înțelegem JavaScript asincron.

JavaScript asincron

Limbajul de programare asincron se concentrează pe îmbunătățirea performanței aplicației. Reapelurile pot fi utilizate în astfel de scenarii. Putem analiza comportamentul asincron al JavaScript prin exemplul de mai jos:

buclă dactilografiată pentru fiecare
 function greet(){ console.log(&apos;greet after 1 second&apos;) } setTimeout(greet, 1000) 

Din exemplul de mai sus, funcția setTimeout preia un callback și timpul în milisecunde ca argumente. Reapelarea este invocată după timpul menționat (aici 1). Pe scurt, funcția va aștepta 1s pentru execuția sa. Acum, aruncați o privire la codul de mai jos:

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

Ieșire:

 first Second greet after 1 second 

Din codul de mai sus, mesajele de jurnal după setTimeout vor fi executate mai întâi în timp ce cronometrul va trece. Prin urmare, rezultă o secundă și apoi mesajul de salut după un interval de timp de 1 secundă.

În JavaScript, setTimeout este o funcție asincronă. Ori de câte ori apelăm funcția setTimeout, aceasta înregistrează o funcție de apel invers (greet în acest caz) care urmează să fie executată după întârzierea specificată. Cu toate acestea, nu blochează execuția codului ulterior.

În exemplul de mai sus, mesajele de jurnal sunt instrucțiunile sincrone care se execută imediat. Ele nu depind de funcția setTimeout. Prin urmare, ei execută și înregistrează mesajele respective pe consolă fără a aștepta întârzierea specificată în setTimeout.

Între timp, bucla de evenimente din JavaScript se ocupă de sarcinile asincrone. În acest caz, așteaptă să treacă întârzierea specificată (1 secundă), iar după ce a trecut acel timp, preia funcția de apel invers (salut) și o execută.

Astfel, celălalt cod după funcția setTimeout se executa în timp ce rula în fundal. Acest comportament permite JavaScript să efectueze alte sarcini în timp ce așteaptă finalizarea operației asincrone.

Trebuie să înțelegem stiva de apeluri și coada de apel invers pentru a gestiona evenimentele asincrone în JavaScript.

Luați în considerare imaginea de mai jos:

Callback Hell în JavaScript

Din imaginea de mai sus, un motor JavaScript tipic constă dintr-o memorie heap și o stivă de apeluri. Stiva de apeluri execută tot codul fără a aștepta când este împins în stivă.

Memoria heap este responsabilă pentru alocarea memoriei pentru obiecte și funcții în timpul rulării ori de câte ori sunt necesare.

Acum, motoarele noastre de browser constau din mai multe API-uri web, cum ar fi DOM, setTimeout, console, fetch etc., iar motorul poate accesa aceste API-uri folosind obiectul fereastră globală. În pasul următor, unele bucle de evenimente joacă rolul de gatekeeper care preia cererile de funcții în coada de apel invers și le împinge în stivă. Aceste funcții, cum ar fi setTimeout, necesită un anumit timp de așteptare.

Acum, să revenim la exemplul nostru, funcția setTimeout; când funcția este întâlnită, temporizatorul este înregistrat în coada de apel invers. După aceasta, restul codului este împins în stiva de apeluri și este executat odată ce funcția atinge limita temporizatorului, este expirat, iar coada de apel invers împinge funcția de apel invers, care are logica specificată și este înregistrată în funcția de timeout. . Astfel, acesta va fi executat după timpul specificat.

Scenarii de apel invers

Acum, am discutat despre apeluri inverse, sincrone, asincrone și alte subiecte relevante pentru iadul callback-ului. Să înțelegem ce este iadul callback în JavaScript.

Situația în care sunt imbricate mai multe apeluri inverse este cunoscută sub numele de iadul callback-ului, deoarece forma codului său arată ca o piramidă, care este numită și „piramida morții”.

Iadul de apel invers face mai dificilă înțelegerea și menținerea codului. În mare parte, putem vedea această situație în timp ce lucrăm în nodul JS. De exemplu, luați în considerare exemplul de mai jos:

 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... } }) 

În exemplul de mai sus, getUserData ia un nume de utilizator care depinde de lista de articole sau trebuie extras răspunsul getArticles care se află în interiorul articolului. getAddress are, de asemenea, o dependență similară, care depinde de răspunsul getUserData. Această situație se numește callback hell.

Funcționarea internă a iadului de apel invers poate fi înțeleasă cu exemplul de mai jos:

Să înțelegem că trebuie să îndeplinim sarcina A. Pentru a efectua o sarcină, avem nevoie de câteva date din sarcina B. În mod similar; avem sarcini diferite care depind unele de altele și se execută asincron. Astfel, creează o serie de funcții de apel invers.

Să înțelegem promisiunile din JavaScript și cum creează operații asincrone, permițându-ne să evităm să scriem apeluri imbricate.

JavaScript promite

În JavaScript, promisiunile au fost introduse în ES6. Este un obiect cu un strat sintactic. Datorită comportamentului său asincron, este o modalitate alternativă de a evita scrierea apelurilor pentru operațiuni asincrone. În zilele noastre, API-urile web precum fetch() sunt implementate folosind promising, care oferă o modalitate eficientă de a accesa datele de pe server. De asemenea, a îmbunătățit lizibilitatea codului și este o modalitate de a evita scrierea apelurilor imbricate.

Promisiunile din viața reală exprimă încredere între două sau mai multe persoane și o asigurare că un anumit lucru se va întâmpla cu siguranță. În JavaScript, o promisiune este un obiect care asigură producerea unei singure valori în viitor (când este necesar). Promise în JavaScript este folosit pentru gestionarea și abordarea operațiunilor asincrone.

Promisiunea returnează un obiect care asigură și reprezintă finalizarea sau eșecul operațiunilor asincrone și rezultatul acestuia. Este un proxy pentru o valoare fără a cunoaște rezultatul exact. Este util pentru acțiunile asincrone pentru a oferi o eventuală valoare de succes sau motiv de eșec. Astfel, metodele asincrone returnează valorile ca o metodă sincronă.

În general, promisiunile au următoarele trei stări:

  • Îndeplinită: starea îndeplinită este atunci când o acțiune aplicată a fost rezolvată sau finalizată cu succes.
  • În așteptare: starea în așteptare este atunci când cererea este în proces, iar acțiunea aplicată nu a fost nici rezolvată, nici respinsă și este încă în starea inițială.
  • Respins: starea respinsă este atunci când acțiunea aplicată a fost respinsă, determinând eșecul operației dorite. Cauza respingerii poate fi orice, inclusiv ca serverul să fie oprit.

Sintaxa pentru promisiuni:

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

Mai jos este un exemplu de scriere a promisiunilor:

Acesta este un exemplu de a scrie o promisiune.

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

În exemplul de mai sus, putem vedea cum putem folosi eficient promisiunile pentru a face o solicitare de la server. Putem observa că lizibilitatea codului este crescută în codul de mai sus decât în ​​apelurile inverse. Promises oferă metode precum .then() și .catch(), care ne permit să gestionăm starea operațiunii în caz de succes sau eșec. Putem preciza cazurile pentru diferitele stări ale promisiunilor.

Async/Await în JavaScript

Este o altă modalitate de a evita utilizarea apelurilor imbricate. Async/ Await ne permite să folosim promisiunile mult mai eficient. Putem evita folosirea înlănțuirii metodei .then() sau .catch(). Aceste metode depind și de funcțiile de apel invers.

Async/Await poate fi utilizat cu precizie cu Promise pentru a îmbunătăți performanța aplicației. A rezolvat intern promisiunile și a oferit rezultatul. De asemenea, din nou, este mai ușor de citit decât metodele () sau catch().

Nu putem folosi Async/Await cu funcțiile normale de apel invers. Pentru a o folosi, trebuie să facem o funcție asincronă scriind un cuvânt cheie asincron înainte de cuvântul cheie de funcție. Cu toate acestea, intern folosește și înlănțuirea.

Mai jos este un exemplu de 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(); 

Pentru a utiliza Async/Await, funcția trebuie specificată cu cuvântul cheie async, iar cuvântul cheie await trebuie scris în interiorul funcției. Async-ul își va opri execuția până când Promisiunea este rezolvată sau respinsă. Acesta va fi reluat când Promisiunea este îndeplinită. Odată rezolvată, valoarea expresiei await va fi stocată în variabila care o deține.

Rezumat:

Pe scurt, putem evita apelurile imbricate folosind promisiunile și async/wait. În afară de acestea, putem urma și alte abordări, cum ar fi scrierea de comentarii și împărțirea codului în componente separate poate fi de asemenea utilă. Dar, în zilele noastre, dezvoltatorii preferă utilizarea async/wait.

întregul java în șir

Concluzie:

Iadul de apel invers în JavaScript este denumit o situație în care se execută o cantitate excesivă de funcții de apel invers imbricate. Reduce lizibilitatea codului și întreținerea. Situația iadului de apel invers apare de obicei atunci când aveți de-a face cu operațiuni de solicitare asincrone, cum ar fi efectuarea de cereri API multiple sau gestionarea evenimentelor cu dependențe complexe.

Pentru a înțelege mai bine iadul callback în JavaScript.

JavaScript consideră totul ca un obiect, cum ar fi șiruri de caractere, matrice și funcții. Prin urmare, conceptul de apel invers ne permite să transmitem funcția ca argument unei alte funcții. Funcția de apel invers va finaliza mai întâi execuția, iar funcția părinte va fi executată mai târziu.

Funcțiile de apel invers sunt executate asincron și permit codului să continue să ruleze fără a aștepta finalizarea sarcinii asincrone.