JavaScript is een asynchrone (niet-blokkerende) programmeertaal met één thread, wat betekent dat er slechts één proces tegelijk kan worden uitgevoerd.
In programmeertalen verwijst callback hell over het algemeen naar een ineffectieve manier om code te schrijven met asynchrone oproepen. Het wordt ook wel de Piramide van het onheil genoemd.
De callback-hel in JavaScript wordt een situatie genoemd waarin een buitensporig aantal geneste callback-functies worden uitgevoerd. Het vermindert de leesbaarheid en het onderhoud van de code. De callback hell-situatie doet zich doorgaans voor bij het omgaan met asynchrone verzoekbewerkingen, zoals het doen van meerdere API-verzoeken of het afhandelen van gebeurtenissen met complexe afhankelijkheden.
Om de callback-hel in JavaScript beter te begrijpen, moet u eerst de callbacks en gebeurtenislussen in JavaScript begrijpen.
Terugbelverzoeken in JavaScript
JavaScript beschouwt alles als een object, zoals tekenreeksen, arrays en functies. Daarom stelt het callback-concept ons in staat de functie als argument door te geven aan een andere functie. De callback-functie voltooit eerst de uitvoering en de ouderfunctie wordt later uitgevoerd.
De callback-functies worden asynchroon uitgevoerd en zorgen ervoor dat de code kan blijven draaien zonder te wachten totdat de asynchrone taak is voltooid. Wanneer meerdere asynchrone taken worden gecombineerd en elke taak afhankelijk is van de vorige taak, wordt de codestructuur ingewikkeld.
Laten we het gebruik en het belang van de callbacks begrijpen. Laten we als voorbeeld aannemen dat we een functie hebben die drie parameters nodig heeft, één string en twee getallen. We willen enige uitvoer op basis van de tekenreekstekst met meerdere voorwaarden.
Beschouw het onderstaande voorbeeld:
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))
Uitgang:
30 20
De bovenstaande code werkt prima, maar we moeten meer taken toevoegen om de code schaalbaar te maken. Het aantal voorwaardelijke instructies zal ook blijven toenemen, wat zal leiden tot een rommelige codestructuur die geoptimaliseerd en leesbaar moet zijn.
We kunnen de code dus als volgt op een betere manier herschrijven:
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))
Uitgang:
30 20
Toch zal de output hetzelfde zijn. Maar in het bovenstaande voorbeeld hebben we de afzonderlijke functietekst gedefinieerd en de functie doorgegeven als een callback-functie aan de functie ExpectedResult. Als we dus de functionaliteit van de verwachte resultaten willen uitbreiden, zodat we een ander functionerend lichaam met een andere bewerking kunnen creëren en dit kunnen gebruiken als callback-functie, zal dit het gemakkelijker maken om de code te begrijpen en de leesbaarheid ervan te verbeteren.
Er zijn nog andere voorbeelden van callbacks beschikbaar in ondersteunde JavaScript-functies. Een paar veelvoorkomende voorbeelden zijn gebeurtenislisteners en arrayfuncties zoals toewijzen, verkleinen, filteren, enz.
Om het beter te begrijpen, moeten we de pass-by-value en pass-by-referentie van JavaScript begrijpen.
JavaScript ondersteunt twee soorten gegevenstypen: primitief en niet-primitief. Primitieve gegevenstypen zijn ongedefinieerd, null, string en boolean, die niet kunnen worden gewijzigd, of we kunnen relatief onveranderlijk zeggen; niet-primitieve gegevenstypen zijn arrays, functies en objecten die kunnen worden gewijzigd of veranderbaar.
Pass by reference geeft het referentieadres van een entiteit door, zoals een functie als argument kan worden opgevat. Dus als de waarde binnen die functie wordt gewijzigd, verandert dit de oorspronkelijke waarde, die beschikbaar is buiten de functie.
Ter vergelijking: het pass-by-value-concept verandert de oorspronkelijke waarde niet, die beschikbaar is buiten de functietekst. In plaats daarvan kopieert het de waarde naar twee verschillende locaties met behulp van hun geheugen. JavaScript identificeerde alle objecten aan de hand van hun referentie.
In JavaScript luistert de addEventListener naar gebeurtenissen zoals klikken, mouseover en mouseout en neemt het tweede argument als een functie die wordt uitgevoerd zodra de gebeurtenis wordt geactiveerd. Deze functie wordt gebruikt als referentieconcept en zonder haakjes doorgegeven.
Beschouw het onderstaande voorbeeld; in dit voorbeeld hebben we een begroetingsfunctie als argument doorgegeven aan de addEventListener als de callback-functie. Het wordt aangeroepen wanneer de klikgebeurtenis wordt geactiveerd:
Test.html:
de mooiste glimlach ter wereld
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)
Uitgang:
In het bovenstaande voorbeeld hebben we een begroetingsfunctie als argument doorgegeven aan de addEventListener als de callback-functie. Het wordt aangeroepen wanneer de klikgebeurtenis wordt geactiveerd.
Op dezelfde manier is het filter ook een voorbeeld van de callback-functie. Als we een filter gebruiken om een array te herhalen, zal er een andere callback-functie als argument nodig zijn om de arraygegevens te verwerken. Beschouw het onderstaande voorbeeld; in dit voorbeeld gebruiken we de functie groter om het getal groter dan 5 in de array af te drukken. We gebruiken de isGreater-functie als callback-functie in de filtermethode.
const arr = [3,10,6,7] const isGreater = num => num > 5 console.log(arr.filter(isGreater))
Uitgang:
[ 10, 6, 7 ]
Het bovenstaande voorbeeld laat zien dat de grotere functie wordt gebruikt als callback-functie in de filtermethode.
Laten we, om de callbacks en gebeurtenislussen in JavaScript beter te begrijpen, synchrone en asynchrone JavaScript bespreken:
Synchrone JavaScript
Laten we begrijpen wat de kenmerken zijn van een synchrone programmeertaal. Synchronisch programmeren heeft de volgende kenmerken:
Uitvoering blokkeren: De synchrone programmeertaal ondersteunt de blokkerende uitvoeringstechniek, wat betekent dat het de uitvoering van volgende instructies blokkeert (de bestaande instructies zullen worden uitgevoerd). Zo wordt de voorspelbare en deterministische uitvoering van de instructies bereikt.
Sequentiële stroom: Synchronisch programmeren ondersteunt de sequentiële uitvoeringsstroom, wat betekent dat elke instructie op een sequentiële manier, de een na de ander, wordt uitgevoerd. Het taalprogramma wacht tot een instructie is voltooid voordat hij doorgaat naar de volgende.
Eenvoud: Vaak wordt synchrone programmering als gemakkelijk te begrijpen beschouwd, omdat we de volgorde van de uitvoeringsstroom kunnen voorspellen. Over het algemeen is het lineair en gemakkelijk te voorspellen. De kleine applicaties zijn goed te ontwikkelen op deze talen, omdat deze de kritische volgorde van bewerkingen aankunnen.
Directe foutafhandeling: In een synchrone programmeertaal is het afhandelen van fouten heel eenvoudig. Als er een fout optreedt wanneer een instructie wordt uitgevoerd, wordt er een fout gegenereerd en kan het programma deze opvangen.
In een notendop heeft synchroon programmeren twee kernfuncties, dat wil zeggen dat één enkele taak tegelijk wordt uitgevoerd en dat de volgende reeks volgende taken pas wordt aangepakt zodra de huidige taak is voltooid. Daarbij volgt een sequentiële code-uitvoering.
Dit gedrag van de programmering wanneer een instructie wordt uitgevoerd, creëert een situatie van blokcode, omdat elke taak moet wachten tot de vorige taak is voltooid.
Maar als mensen over JavaScript praten, is het altijd een raadselachtig antwoord geweest, of het nu synchroon of asynchroon is.
Wanneer we in de hierboven besproken voorbeelden een functie gebruikten als callback in de filterfunctie, werd deze synchroon uitgevoerd. Daarom wordt dit een synchrone uitvoering genoemd. De filterfunctie moet wachten tot de grotere functie zijn uitvoering heeft voltooid.
Daarom wordt de callback-functie ook wel callbacks blokkeren genoemd, omdat deze de uitvoering blokkeert van de bovenliggende functie waarin deze werd aangeroepen.
In de eerste plaats wordt JavaScript beschouwd als single-threaded, synchroon en blokkerend van aard. Maar met behulp van een paar benaderingen kunnen we het asynchroon laten werken op basis van verschillende scenario's.
Laten we nu het asynchrone JavaScript begrijpen.
Asynchrone JavaScript
De asynchrone programmeertaal richt zich op het verbeteren van de prestaties van de applicatie. De callbacks kunnen in dergelijke scenario's worden gebruikt. We kunnen het asynchrone gedrag van JavaScript analyseren aan de hand van het onderstaande voorbeeld:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000)
Uit het bovenstaande voorbeeld neemt de functie setTimeout een callback en tijd in milliseconden als argumenten. De callback wordt aangeroepen na de genoemde tijd (hier 1s). Kort gezegd wacht de functie 1 seconde op uitvoering ervan. Kijk nu eens naar de onderstaande code:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000) console.log('first') console.log('Second')
Uitgang:
first Second greet after 1 second
Uit de bovenstaande code worden de logberichten na de setTimeout eerst uitgevoerd terwijl de timer verstrijkt. Het resultaat is dus één seconde en vervolgens het begroetingsbericht na een tijdsinterval van 1 seconde.
In JavaScript is setTimeout een asynchrone functie. Telkens wanneer we de functie setTimeout aanroepen, registreert deze een callback-functie (in dit geval begroeten) die na de opgegeven vertraging moet worden uitgevoerd. Het blokkeert echter niet de uitvoering van de volgende code.
In het bovenstaande voorbeeld zijn de logberichten de synchrone instructies die onmiddellijk worden uitgevoerd. Ze zijn niet afhankelijk van de setTimeout-functie. Daarom voeren ze hun respectievelijke berichten uit en loggen ze in de console zonder te wachten op de vertraging die is opgegeven in setTimeout.
Ondertussen handelt de gebeurtenislus in JavaScript de asynchrone taken af. In dit geval wacht het tot de gespecificeerde vertraging (1 seconde) is verstreken, en nadat die tijd is verstreken, pakt het de callback-functie (begroet) op en voert deze uit.
De andere code na de setTimeout-functie werd dus uitgevoerd terwijl deze op de achtergrond werd uitgevoerd. Door dit gedrag kan JavaScript andere taken uitvoeren terwijl wordt gewacht tot de asynchrone bewerking is voltooid.
We moeten de call-stack en callback-wachtrij begrijpen om de asynchrone gebeurtenissen in JavaScript af te handelen.
Beschouw de onderstaande afbeelding:
Uit de bovenstaande afbeelding blijkt dat een typische JavaScript-engine bestaat uit een heap-geheugen en een call-stack. De call-stack voert alle code uit zonder te wachten wanneer deze naar de stack wordt geduwd.
Het heapgeheugen is verantwoordelijk voor het toewijzen van geheugen aan objecten en functies tijdens runtime wanneer deze nodig zijn.
Nu bestaan onze browser-engines uit verschillende web-API's zoals DOM, setTimeout, console, fetch, enz., en de engine heeft toegang tot deze API's met behulp van het globale window-object. In de volgende stap spelen sommige gebeurtenislussen de rol van poortwachter die functieverzoeken binnen de callback-wachtrij oppikt en deze in de stapel duwt. Deze functies, zoals setTimeout, vereisen een bepaalde wachttijd.
Laten we nu teruggaan naar ons voorbeeld, de setTimeout-functie; wanneer de functie wordt aangetroffen, wordt de timer geregistreerd in de callback-wachtrij. Hierna wordt de rest van de code in de call-stack geduwd en uitgevoerd zodra de functie de timerlimiet bereikt, deze is verlopen en de callback-wachtrij pusht de callback-functie, die de gespecificeerde logica heeft en is geregistreerd in de time-outfunctie. . Het wordt dus na de opgegeven tijd uitgevoerd.
Callback Hell-scenario's
Nu hebben we de callbacks, synchrone, asynchroon en andere relevante onderwerpen voor de callback-hel besproken. Laten we begrijpen wat de callback-hel is in JavaScript.
De situatie waarin meerdere callbacks zijn genest, staat bekend als de callback-hel, omdat de codevorm eruitziet als een piramide, die ook wel de 'piramide van het onheil' wordt genoemd.
De callback-hel maakt het moeilijker om de code te begrijpen en te onderhouden. We kunnen deze situatie vooral zien tijdens het werken in knooppunt JS. Beschouw bijvoorbeeld het onderstaande voorbeeld:
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... } })
In het bovenstaande voorbeeld neemt getUserData een gebruikersnaam die afhankelijk is van de artikellijst of die moet worden geëxtraheerd get Articles-antwoord dat zich in het artikel bevindt. getAddress heeft ook een soortgelijke afhankelijkheid, die afhankelijk is van het antwoord van getUserData. Deze situatie wordt de callback-hel genoemd.
De interne werking van de callback-hel kan worden begrepen met het onderstaande voorbeeld:
Laten we begrijpen dat we taak A moeten uitvoeren. Om een taak uit te voeren, hebben we enkele gegevens nodig van taak B. Op dezelfde manier; we hebben verschillende taken die van elkaar afhankelijk zijn en asynchroon worden uitgevoerd. Het creëert dus een reeks callback-functies.
Laten we de beloften in JavaScript begrijpen en begrijpen hoe ze asynchrone bewerkingen creëren, waardoor we het schrijven van geneste callbacks kunnen vermijden.
JavaScript-beloften
In JavaScript werden beloften geïntroduceerd in ES6. Het is een object met een syntactische coating. Vanwege het asynchrone gedrag is het een alternatieve manier om te voorkomen dat de callbacks voor asynchrone bewerkingen worden geschreven. Tegenwoordig worden web-API's zoals fetch() geïmplementeerd met behulp van de veelbelovende, die een efficiënte manier biedt om toegang te krijgen tot de gegevens van de server. Het verbeterde ook de leesbaarheid van de code en is een manier om het schrijven van geneste callbacks te voorkomen.
Beloften in het echte leven drukken vertrouwen uit tussen twee of meer personen en de verzekering dat iets bepaalds zeker zal gebeuren. In JavaScript is een belofte een object dat ervoor zorgt dat er in de toekomst één enkele waarde wordt geproduceerd (indien nodig). Promise in JavaScript wordt gebruikt voor het beheren en aanpakken van asynchrone operaties.
De Promise retourneert een object dat de voltooiing of mislukking van asynchrone bewerkingen en de uitvoer ervan garandeert en vertegenwoordigt. Het is een proxy voor een waarde zonder de exacte uitvoer te kennen. Voor asynchrone acties is het handig om een uiteindelijke succeswaarde of mislukkingsreden op te geven. De asynchrone methoden retourneren de waarden dus als een synchrone methode.
Over het algemeen hebben de beloften de volgende drie toestanden:
- Vervuld: De status Vervuld is wanneer een toegepaste actie is opgelost of succesvol is voltooid.
- In behandeling: de status In behandeling is wanneer het verzoek in behandeling is en de toegepaste actie niet is opgelost of afgewezen en zich nog in de oorspronkelijke staat bevindt.
- Afgewezen: De status afgewezen is wanneer de toegepaste actie is afgewezen, waardoor de gewenste bewerking mislukt. De oorzaak van de afwijzing kan van alles zijn, inclusief het uitvallen van de server.
De syntaxis voor de beloften:
let newPromise = new Promise(function(resolve, reject) { // asynchronous call is made //Resolve or reject the data });
Hieronder ziet u een voorbeeld van het schrijven van de beloften:
Dit is een voorbeeld van het schrijven van een belofte.
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))
In het bovenstaande voorbeeld kunnen we zien hoe we de beloften efficiënt kunnen gebruiken om een verzoek van de server te doen. We kunnen zien dat de leesbaarheid van de code in de bovenstaande code groter is dan in de callbacks. Promises bieden methoden als .then() en .catch(), waarmee we de status van de bewerking kunnen afhandelen in geval van succes of mislukking. We kunnen de gevallen specificeren voor de verschillende staten van de beloften.
Async/Await in JavaScript
Het is een andere manier om het gebruik van geneste callbacks te vermijden. Met Async/Await kunnen we de beloften veel efficiënter gebruiken. We kunnen het gebruik van .then() of .catch() methodeketens vermijden. Deze methoden zijn ook afhankelijk van de callback-functies.
Async/Await kan nauwkeurig worden gebruikt met Promise om de prestaties van de applicatie te verbeteren. Het loste intern de beloftes op en zorgde voor resultaat. Ook is het, nogmaals, beter leesbaar dan de methoden () of catch().
We kunnen Async/Await niet gebruiken met de normale callback-functies. Om het te gebruiken, moeten we een functie asynchroon maken door een asynchroonsleutelwoord vóór het functiesleutelwoord te schrijven. Intern wordt echter ook gebruik gemaakt van chaining.
Hieronder ziet u een voorbeeld van 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();
Om Async/Await te gebruiken, moet de functie worden opgegeven met het async-trefwoord, en het await-trefwoord moet in de functie worden geschreven. De async stopt de uitvoering ervan totdat de belofte is opgelost of afgewezen. Het zal worden hervat zodra de belofte is uitgedeeld. Eenmaal opgelost, wordt de waarde van de await-expressie opgeslagen in de variabele die deze bevat.
Samenvatting:
In een notendop kunnen we geneste callbacks vermijden door de beloften en async/await te gebruiken. Afgezien hiervan kunnen we andere benaderingen volgen, zoals het schrijven van commentaar, en het opsplitsen van de code in afzonderlijke componenten kan ook nuttig zijn. Maar tegenwoordig geven de ontwikkelaars de voorkeur aan het gebruik van async/await.
Conclusie:
De callback-hel in JavaScript wordt een situatie genoemd waarin een buitensporig aantal geneste callback-functies worden uitgevoerd. Het vermindert de leesbaarheid en het onderhoud van de code. De callback hell-situatie doet zich doorgaans voor bij het omgaan met asynchrone verzoekbewerkingen, zoals het doen van meerdere API-verzoeken of het afhandelen van gebeurtenissen met complexe afhankelijkheden.
Om de callback-hel in JavaScript beter te begrijpen.
JavaScript beschouwt alles als een object, zoals tekenreeksen, arrays en functies. Daarom stelt het callback-concept ons in staat de functie als argument door te geven aan een andere functie. De callback-functie voltooit eerst de uitvoering en de ouderfunctie wordt later uitgevoerd.
De callback-functies worden asynchroon uitgevoerd en zorgen ervoor dat de code kan blijven draaien zonder te wachten totdat de asynchrone taak is voltooid.