logo

Rückrufhölle in JavaScript

JavaScript ist eine asynchrone (nicht blockierende) und Single-Threaded-Programmiersprache, was bedeutet, dass jeweils nur ein Prozess ausgeführt werden kann.

In Programmiersprachen bezieht sich Callback-Hölle im Allgemeinen auf eine ineffektive Art, Code mit asynchronen Aufrufen zu schreiben. Sie ist auch als Pyramide des Untergangs bekannt.

Als Callback-Hölle bezeichnet man in JavaScript eine Situation, in der übermäßig viele verschachtelte Callback-Funktionen ausgeführt werden. Es reduziert die Lesbarkeit und Wartung des Codes. Die Callback-Höllen-Situation tritt typischerweise auf, wenn es um asynchrone Anforderungsvorgänge geht, z. B. um mehrere API-Anfragen zu stellen oder Ereignisse mit komplexen Abhängigkeiten zu verarbeiten.

Um die Callback-Hölle in JavaScript besser zu verstehen, verstehen Sie zunächst die Callbacks und Ereignisschleifen in JavaScript.

Rückrufe in JavaScript

JavaScript betrachtet alles als Objekt, beispielsweise Zeichenfolgen, Arrays und Funktionen. Daher ermöglicht uns das Callback-Konzept, die Funktion als Argument an eine andere Funktion zu übergeben. Die Rückruffunktion schließt die Ausführung zuerst ab und die übergeordnete Funktion wird später ausgeführt.

Die Rückruffunktionen werden asynchron ausgeführt und ermöglichen die weitere Ausführung des Codes, ohne auf den Abschluss der asynchronen Aufgabe warten zu müssen. Wenn mehrere asynchrone Aufgaben kombiniert werden und jede Aufgabe von ihrer vorherigen Aufgabe abhängt, wird die Codestruktur kompliziert.

Lassen Sie uns die Verwendung und Bedeutung der Rückrufe verstehen. Nehmen wir ein Beispiel an, wir haben eine Funktion, die drei Parameter, eine Zeichenfolge und zwei Zahlen akzeptiert. Wir möchten eine Ausgabe basierend auf dem Zeichenfolgentext mit mehreren Bedingungen.

Betrachten Sie das folgende Beispiel:

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

Ausgabe:

 30 20 

Der obige Code funktioniert einwandfrei, wir müssen jedoch weitere Aufgaben hinzufügen, um den Code skalierbar zu machen. Auch die Anzahl der bedingten Anweisungen wird weiter zunehmen, was zu einer unübersichtlichen Codestruktur führt, die optimiert und lesbar sein muss.

So können wir den Code wie folgt besser umschreiben:

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

Ausgabe:

 30 20 

Dennoch wird die Ausgabe dieselbe sein. Im obigen Beispiel haben wir jedoch seinen separaten Funktionskörper definiert und die Funktion als Rückruffunktion an die Funktion „expectedResult“ übergeben. Wenn wir also die Funktionalität der erwarteten Ergebnisse erweitern möchten, sodass wir einen weiteren funktionierenden Körper mit einer anderen Operation erstellen und ihn als Rückruffunktion verwenden können, wird dies das Verständnis erleichtern und die Lesbarkeit des Codes verbessern.

Es gibt weitere verschiedene Beispiele für Rückrufe, die in unterstützten JavaScript-Funktionen verfügbar sind. Einige gängige Beispiele sind Ereignis-Listener und Array-Funktionen wie Zuordnen, Reduzieren, Filtern usw.

Um es besser zu verstehen, sollten wir JavaScripts Pass-by-Value und Pass-by-Reference verstehen.

versteckte Apps auf diesem Gerät

JavaScript unterstützt zwei Arten von Datentypen: primitive und nicht-primitive. Primitive Datentypen sind undefiniert, null, string und boolean, die nicht geändert werden können, oder wir können vergleichsweise unveränderlich sagen; Nicht-primitive Datentypen sind Arrays, Funktionen und Objekte, die geändert oder veränderbar sind.

Bei der Referenzübergabe wird die Referenzadresse einer Entität übergeben, so wie eine Funktion als Argument verwendet werden kann. Wenn also der Wert innerhalb dieser Funktion geändert wird, ändert sich auch der ursprüngliche Wert, der außerhalb der Funktion verfügbar ist.

Im Vergleich dazu ändert das Konzept der Wertübergabe nicht seinen ursprünglichen Wert, der außerhalb des Funktionskörpers verfügbar ist. Stattdessen wird der Wert mithilfe ihres Speichers an zwei verschiedene Speicherorte kopiert. JavaScript identifizierte alle Objekte anhand ihrer Referenz.

In JavaScript lauscht der addEventListener auf Ereignisse wie Click, Mouseover und Mouseout und verwendet das zweite Argument als Funktion, die ausgeführt wird, sobald das Ereignis ausgelöst wird. Diese Funktion wird als Referenzkonzept verwendet und ohne Klammern übergeben.

Betrachten Sie das folgende Beispiel. In diesem Beispiel haben wir eine Begrüßungsfunktion als Argument als Rückruffunktion an addEventListener übergeben. Es wird aufgerufen, wenn das Klickereignis ausgelöst wird:

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) 

Ausgabe:

Rückrufhölle in JavaScript

Im obigen Beispiel haben wir eine Begrüßungsfunktion als Argument als Rückruffunktion an den addEventListener übergeben. Es wird aufgerufen, wenn das Klickereignis ausgelöst wird.

Ebenso ist der Filter auch ein Beispiel für die Rückruffunktion. Wenn wir einen Filter verwenden, um ein Array zu iterieren, benötigt dieser eine weitere Rückruffunktion als Argument, um die Array-Daten zu verarbeiten. Betrachten Sie das folgende Beispiel. In diesem Beispiel verwenden wir die Funktion „Greater“, um die Zahl größer als 5 im Array auszugeben. Wir verwenden die Funktion isGreater als Callback-Funktion in der Filtermethode.

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

Ausgabe:

Update in SQL mit Join
 [ 10, 6, 7 ] 

Das obige Beispiel zeigt, dass die größere Funktion als Rückruffunktion in der Filtermethode verwendet wird.

Um die Rückrufe und Ereignisschleifen in JavaScript besser zu verstehen, besprechen wir synchrones und asynchrones JavaScript:

Synchrones JavaScript

Lassen Sie uns verstehen, was die Funktionen einer synchronen Programmiersprache sind. Die synchrone Programmierung weist die folgenden Funktionen auf:

Ausführung blockieren: Die synchrone Programmiersprache unterstützt die Blockierungsausführungstechnik, was bedeutet, dass sie die Ausführung nachfolgender Anweisungen blockiert. Die vorhandenen Anweisungen werden ausgeführt. Dadurch wird eine vorhersehbare und deterministische Ausführung der Anweisungen erreicht.

Sequentielle Abfolge: Die synchrone Programmierung unterstützt den sequenziellen Ausführungsfluss, was bedeutet, dass jede Anweisung sequentiell wie eine nach der anderen ausgeführt wird. Das Sprachprogramm wartet auf den Abschluss einer Anweisung, bevor es mit der nächsten fortfährt.

Einfachheit: Oft wird die synchrone Programmierung als leicht verständlich angesehen, da wir die Reihenfolge des Ausführungsflusses vorhersagen können. Im Allgemeinen ist es linear und leicht vorherzusagen. Die kleinen Anwendungen lassen sich gut in diesen Sprachen entwickeln, da sie die kritische Reihenfolge von Vorgängen bewältigen können.

int-Zeichenfolge

Direkte Fehlerbehandlung: In einer synchronen Programmiersprache ist die Fehlerbehandlung sehr einfach. Wenn beim Ausführen einer Anweisung ein Fehler auftritt, wird ein Fehler ausgegeben und das Programm kann ihn abfangen.

Kurz gesagt verfügt die synchrone Programmierung über zwei Kernfunktionen: Es wird jeweils nur eine Aufgabe ausgeführt, und die nächste Reihe folgender Aufgaben wird erst dann bearbeitet, wenn die aktuelle Aufgabe abgeschlossen ist. Dabei erfolgt eine sequentielle Codeausführung.

Dieses Verhalten der Programmierung bei der Ausführung einer Anweisung erzeugt eine Blockcode-Situation, da jeder Job auf den Abschluss des vorherigen Jobs warten muss.

Aber wenn Leute über JavaScript sprechen, war die Antwort immer rätselhaft, ob es synchron oder asynchron ist.

Wenn wir in den oben besprochenen Beispielen eine Funktion als Rückruf in der Filterfunktion verwendeten, wurde diese synchron ausgeführt. Daher spricht man von einer synchronen Ausführung. Die Filterfunktion muss warten, bis die übergeordnete Funktion ihre Ausführung abgeschlossen hat.

Daher wird die Callback-Funktion auch als blockierende Callbacks bezeichnet, da sie die Ausführung der übergeordneten Funktion blockiert, in der sie aufgerufen wurde.

In erster Linie gilt JavaScript als Single-Threaded-synchroner und blockierender Charakter. Mit ein paar Ansätzen können wir jedoch dafür sorgen, dass es auf der Grundlage verschiedener Szenarien asynchron funktioniert.

Lassen Sie uns nun das asynchrone JavaScript verstehen.

Asynchrones JavaScript

Die asynchrone Programmiersprache konzentriert sich darauf, die Leistung der Anwendung zu verbessern. Die Rückrufe können in solchen Szenarien verwendet werden. Wir können das asynchrone Verhalten von JavaScript anhand des folgenden Beispiels analysieren:

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

Aus dem obigen Beispiel nimmt die Funktion setTimeout einen Rückruf und eine Zeit in Millisekunden als Argumente entgegen. Der Rückruf wird nach der genannten Zeit (hier 1s) aufgerufen. Kurz gesagt, die Funktion wartet 1 Sekunde auf ihre Ausführung. Schauen Sie sich nun den folgenden Code an:

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

Ausgabe:

 first Second greet after 1 second 

Aus dem obigen Code werden die Protokollnachrichten nach setTimeout zuerst ausgeführt, während der Timer abläuft. Es erfolgt also ein Zeitintervall von einer Sekunde und dann die Begrüßungsnachricht nach 1 Sekunde.

In JavaScript ist setTimeout eine asynchrone Funktion. Immer wenn wir die Funktion setTimeout aufrufen, registriert sie eine Rückruffunktion (in diesem Fall „Greet“), die nach der angegebenen Verzögerung ausgeführt wird. Es blockiert jedoch nicht die Ausführung des nachfolgenden Codes.

Im obigen Beispiel sind die Protokollnachrichten die synchronen Anweisungen, die sofort ausgeführt werden. Sie sind nicht von der setTimeout-Funktion abhängig. Daher führen sie ihre jeweiligen Nachrichten aus und protokollieren sie in der Konsole, ohne auf die in setTimeout angegebene Verzögerung zu warten.

Währenddessen übernimmt die Ereignisschleife in JavaScript die asynchronen Aufgaben. In diesem Fall wartet es, bis die angegebene Verzögerung (1 Sekunde) verstrichen ist, und greift nach Ablauf dieser Zeit die Rückruffunktion (Greet) auf und führt sie aus.

Daher wurde der andere Code nach der setTimeout-Funktion ausgeführt, während er im Hintergrund ausgeführt wurde. Dieses Verhalten ermöglicht es JavaScript, andere Aufgaben auszuführen, während es auf den Abschluss des asynchronen Vorgangs wartet.

Wir müssen den Aufrufstapel und die Rückrufwarteschlange verstehen, um die asynchronen Ereignisse in JavaScript zu verarbeiten.

Betrachten Sie das folgende Bild:

Rückrufhölle in JavaScript

Aus dem obigen Bild besteht eine typische JavaScript-Engine aus einem Heap-Speicher und einem Aufrufstapel. Der Aufrufstapel führt den gesamten Code aus, ohne zu warten, wenn er auf den Stapel verschoben wird.

Der Heap-Speicher ist dafür verantwortlich, den Speicher für Objekte und Funktionen zur Laufzeit zuzuweisen, wann immer sie benötigt werden.

Jetzt bestehen unsere Browser-Engines aus mehreren Web-APIs wie DOM, setTimeout, console, fetch usw., und die Engine kann über das globale Fensterobjekt auf diese APIs zugreifen. Im nächsten Schritt übernehmen einige Ereignisschleifen die Rolle eines Gatekeepers, der Funktionsanforderungen in der Rückrufwarteschlange auswählt und in den Stapel schiebt. Diese Funktionen, wie zum Beispiel setTimeout, erfordern eine gewisse Wartezeit.

Kehren wir nun zu unserem Beispiel zurück, der Funktion setTimeout. Wenn die Funktion angetroffen wird, wird der Timer in der Rückrufwarteschlange registriert. Danach wird der Rest des Codes in den Aufrufstapel verschoben und ausgeführt, sobald die Funktion ihr Timerlimit erreicht, abgelaufen ist und die Rückrufwarteschlange die Rückruffunktion weiterleitet, die über die angegebene Logik verfügt und in der Timeout-Funktion registriert ist . Daher wird es nach der angegebenen Zeit ausgeführt.

Callback-Höllenszenarien

Jetzt haben wir die Rückrufe, synchrone, asynchrone und andere relevante Themen für die Rückrufhölle besprochen. Lassen Sie uns verstehen, was die Callback-Hölle in JavaScript ist.

Zeile vs. Spalte

Die Situation, in der mehrere Rückrufe verschachtelt sind, wird als Rückrufhölle bezeichnet, da ihre Codeform wie eine Pyramide aussieht, die auch „Pyramide des Untergangs“ genannt wird.

Die Callback-Hölle macht es schwieriger, den Code zu verstehen und zu warten. Wir können diese Situation meistens sehen, wenn wir im Node JS arbeiten. Betrachten Sie beispielsweise das folgende Beispiel:

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

Im obigen Beispiel nimmt getUserData einen Benutzernamen an, der von der Artikelliste abhängt, oder es muss eine getArticles-Antwort extrahiert werden, die sich im Artikel befindet. getAddress hat auch eine ähnliche Abhängigkeit, die von der Antwort von getUserData abhängt. Diese Situation wird Callback-Hölle genannt.

Die interne Funktionsweise der Callback-Hölle kann anhand des folgenden Beispiels verstanden werden:

Lassen Sie uns verstehen, dass wir Aufgabe A ausführen müssen. Um eine Aufgabe auszuführen, benötigen wir einige Daten von Aufgabe B. Ebenso; Wir haben verschiedene Aufgaben, die voneinander abhängig sind und asynchron ausgeführt werden. Dadurch wird eine Reihe von Rückruffunktionen erstellt.

Lassen Sie uns die Versprechen in JavaScript verstehen und wie sie asynchrone Vorgänge erstellen, sodass wir das Schreiben verschachtelter Rückrufe vermeiden können.

JavaScript verspricht

In JavaScript wurden in ES6 Versprechen eingeführt. Es handelt sich um ein Objekt mit einer syntaktischen Beschichtung. Aufgrund seines asynchronen Verhaltens ist es eine alternative Möglichkeit, das Schreiben von Rückrufen für asynchrone Vorgänge zu vermeiden. Heutzutage werden Web-APIs wie fetch() mit Promise implementiert, was eine effiziente Möglichkeit bietet, auf die Daten vom Server zuzugreifen. Es verbessert auch die Lesbarkeit des Codes und ist eine Möglichkeit, das Schreiben verschachtelter Rückrufe zu vermeiden.

Sortieren Sie eine Arrayliste in Java

Versprechen im wirklichen Leben drücken Vertrauen zwischen zwei oder mehreren Personen aus und geben die Gewissheit, dass etwas Bestimmtes mit Sicherheit passieren wird. In JavaScript ist ein Promise ein Objekt, das sicherstellt, dass in der Zukunft (bei Bedarf) ein einzelner Wert erzeugt wird. Promise wird in JavaScript zum Verwalten und Bewältigen asynchroner Vorgänge verwendet.

Das Promise gibt ein Objekt zurück, das den Abschluss oder Misserfolg asynchroner Vorgänge und deren Ausgabe sicherstellt und darstellt. Es ist ein Proxy für einen Wert, ohne die genaue Ausgabe zu kennen. Für asynchrone Aktionen ist es nützlich, einen eventuellen Erfolgswert oder Fehlergrund anzugeben. Somit geben die asynchronen Methoden die Werte wie eine synchrone Methode zurück.

Im Allgemeinen haben die Versprechen die folgenden drei Zustände:

  • Erfüllt: Der Status „Erfüllt“ liegt vor, wenn eine angewendete Aktion gelöst oder erfolgreich abgeschlossen wurde.
  • Ausstehend: Der Status „Ausstehend“ liegt vor, wenn die Anfrage in Bearbeitung ist und die angewendete Aktion weder gelöst noch abgelehnt wurde und sich immer noch im Anfangsstatus befindet.
  • Abgelehnt: Der Status „Abgelehnt“ liegt vor, wenn die angewendete Aktion abgelehnt wurde, was dazu führt, dass der gewünschte Vorgang fehlschlägt. Der Grund für die Ablehnung kann alles sein, auch ein Serverausfall.

Die Syntax für die Versprechen:

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

Nachfolgend finden Sie ein Beispiel für das Schreiben der Versprechen:

Dies ist ein Beispiel für das Schreiben eines Versprechens.

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

Im obigen Beispiel können wir sehen, wie wir die Versprechen effizient nutzen können, um eine Anfrage vom Server zu stellen. Wir können beobachten, dass die Lesbarkeit des Codes im obigen Code besser ist als in den Rückrufen. Versprechen stellen Methoden wie .then() und .catch() bereit, die es uns ermöglichen, den Operationsstatus im Erfolgs- oder Misserfolgsfall zu verwalten. Wir können die Fälle für die verschiedenen Zustände der Versprechen angeben.

Async/Warten in JavaScript

Dies ist eine weitere Möglichkeit, die Verwendung verschachtelter Rückrufe zu vermeiden. Async/Await ermöglicht es uns, die Versprechen viel effizienter zu nutzen. Wir können die Methodenverkettung .then() oder .catch() vermeiden. Diese Methoden sind auch von den Callback-Funktionen abhängig.

Async/Await kann präzise mit Promise verwendet werden, um die Leistung der Anwendung zu verbessern. Es löste intern die Versprechen ein und lieferte das Ergebnis. Außerdem ist sie wiederum besser lesbar als die Methoden () oder Catch().

Wir können Async/Await nicht mit den normalen Rückruffunktionen verwenden. Um es zu verwenden, müssen wir eine Funktion asynchron machen, indem wir vor dem Funktionsschlüsselwort ein async-Schlüsselwort schreiben. Intern wird jedoch auch die Verkettung verwendet.

Unten finden Sie ein Beispiel für 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(); 

Um Async/Await zu verwenden, muss die Funktion mit dem Schlüsselwort async angegeben werden und das Schlüsselwort „await“ sollte in die Funktion geschrieben werden. Der Async stoppt seine Ausführung, bis das Promise aufgelöst oder abgelehnt wird. Es wird wieder aufgenommen, wenn das Versprechen ausgeteilt wird. Nach der Auflösung wird der Wert des Wait-Ausdrucks in der Variablen gespeichert, die ihn enthält.

Zusammenfassung:

Kurz gesagt: Wir können verschachtelte Rückrufe vermeiden, indem wir die Versprechen und async/await verwenden. Abgesehen davon können wir auch andere Ansätze verfolgen, wie zum Beispiel das Schreiben von Kommentaren, und auch die Aufteilung des Codes in einzelne Komponenten kann hilfreich sein. Heutzutage bevorzugen die Entwickler jedoch die Verwendung von async/await.

Abschluss:

Als Callback-Hölle bezeichnet man in JavaScript eine Situation, in der übermäßig viele verschachtelte Callback-Funktionen ausgeführt werden. Es verringert die Lesbarkeit und Wartung des Codes. Die Callback-Höllen-Situation tritt typischerweise auf, wenn es um asynchrone Anforderungsvorgänge geht, z. B. um mehrere API-Anfragen zu stellen oder Ereignisse mit komplexen Abhängigkeiten zu verarbeiten.

Um die Callback-Hölle in JavaScript besser zu verstehen.

JavaScript betrachtet alles als Objekt, beispielsweise Zeichenfolgen, Arrays und Funktionen. Daher ermöglicht uns das Callback-Konzept, die Funktion als Argument an eine andere Funktion zu übergeben. Die Rückruffunktion schließt die Ausführung zuerst ab und die übergeordnete Funktion wird später ausgeführt.

Die Rückruffunktionen werden asynchron ausgeführt und ermöglichen die weitere Ausführung des Codes, ohne auf den Abschluss der asynchronen Aufgabe warten zu müssen.