Was ist "Callback Hell" und wie und warum löst RX es?

113

Kann jemand eine klare Definition zusammen mit einem einfachen Beispiel geben, das erklärt, was eine "Rückrufhölle" für jemanden ist, der JavaScript und node.js nicht kennt?

Wann (in welchen Einstellungen) tritt das "Callback Hell Problem" auf?

Warum kommt es vor?

Bezieht sich "Rückrufhölle" immer auf asynchrone Berechnungen?

Oder kann "Callback Hell" auch in einer Single-Threaded-Anwendung auftreten?

Ich nahm am Reaktivkurs in Coursera teil und Erik Meijer sagte in einer seiner Vorlesungen, dass RX das Problem der "Rückrufhölle" löst. Ich habe im Coursera-Forum gefragt, was eine "Rückruf-Hölle" ist, aber ich habe keine klare Antwort erhalten.

Können Sie nach der Erläuterung von "Callback Hell" in einem einfachen Beispiel auch zeigen, wie RX das "Callback Hell" -Problem in diesem einfachen Beispiel löst?

jhegedus
quelle

Antworten:

136

1) Was ist eine "Rückruf-Hölle" für jemanden, der Javascript und node.js nicht kennt?

Diese andere Frage enthält einige Beispiele für die Hölle des Javascript-Rückrufs: So vermeiden Sie eine lange Verschachtelung asynchroner Funktionen in Node.js.

Das Problem in Javascript ist, dass die einzige Möglichkeit, eine Berechnung "einzufrieren" und den "Rest" (asynchron) ausführen zu lassen, darin besteht, "den Rest" in einen Rückruf einzufügen.

Angenommen, ich möchte Code ausführen, der folgendermaßen aussieht:

x = getData();
y = getMoreData(x);
z = getMoreData(y);
...

Was passiert, wenn ich jetzt die getData-Funktionen asynchron machen möchte, was bedeutet, dass ich die Möglichkeit habe, einen anderen Code auszuführen, während ich darauf warte, dass sie ihre Werte zurückgeben? In Javascript besteht die einzige Möglichkeit darin, alles, was eine asynchrone Berechnung berührt, mithilfe des Continuation-Passing-Stils neu zu schreiben :

getData(function(x){
    getMoreData(x, function(y){
        getMoreData(y, function(z){ 
            ...
        });
    });
});

Ich glaube nicht, dass ich jemanden davon überzeugen muss, dass diese Version hässlicher ist als die vorherige. :-)

2) Wann (in welchen Einstellungen) tritt das "Callback Hell Problem" auf?

Wenn Sie viele Rückruffunktionen in Ihrem Code haben! Es wird schwieriger, mit ihnen zu arbeiten, je mehr Sie in Ihrem Code haben, und es wird besonders schlimm, wenn Sie Schleifen, Try-Catch-Blöcke und ähnliches ausführen müssen.

Soweit ich weiß, ist die einzige Möglichkeit, in JavaScript eine Reihe von asynchronen Funktionen auszuführen, bei denen eine nach den vorherigen Rückgaben ausgeführt wird, die Verwendung einer rekursiven Funktion. Sie können keine for-Schleife verwenden.

// we would like to write the following
for(var i=0; i<10; i++){
    doSomething(i);
}
blah();

Stattdessen müssen wir möglicherweise schreiben:

function loop(i, onDone){
    if(i >= 10){
        onDone()
    }else{
        doSomething(i, function(){
            loop(i+1, onDone);
        });
     }
}
loop(0, function(){
    blah();
});

//ugh!

Die Anzahl der Fragen, die wir hier bei StackOverflow erhalten, wie man so etwas macht, ist ein Beweis dafür, wie verwirrend es ist :)

3) Warum tritt es auf?

Dies tritt auf, weil in JavaScript die einzige Möglichkeit, eine Berechnung so zu verzögern, dass sie nach der Rückkehr des asynchronen Aufrufs ausgeführt wird, darin besteht, den verzögerten Code in eine Rückruffunktion einzufügen. Sie können Code, der im traditionellen synchronen Stil geschrieben wurde, nicht verzögern, sodass Sie überall verschachtelte Rückrufe erhalten.

4) Oder kann "Callback Hell" auch in einer Single-Threaded-Anwendung auftreten?

Asynchrone Programmierung hat mit Parallelität zu tun, während ein einzelner Thread mit Parallelität zu tun hat. Die beiden Konzepte sind eigentlich nicht dasselbe.

Sie können weiterhin gleichzeitig Code in einem einzelnen Thread-Kontext haben. In der Tat ist JavaScript, die Königin der Callback-Hölle, Single-Threaded.

Was ist der Unterschied zwischen Parallelität und Parallelität?

5) Könnten Sie bitte auch zeigen, wie RX das "Callback Hell Problem" in diesem einfachen Beispiel löst?

Ich weiß insbesondere nichts über RX, aber normalerweise wird dieses Problem gelöst, indem native Unterstützung für asynchrone Berechnungen in der Programmiersprache hinzugefügt wird. Die Implementierungen können variieren und umfassen: Async, Generatoren, Coroutinen und Callcc.

In Python können wir dieses vorherige Schleifenbeispiel folgendermaßen implementieren:

def myLoop():
    for i in range(10):
        doSomething(i)
        yield

myGen = myLoop()

Dies ist nicht der vollständige Code, aber die Idee ist, dass die "Ausbeute" unsere for-Schleife pausiert, bis jemand myGen.next () aufruft. Das Wichtigste ist, dass wir den Code immer noch mit einer for-Schleife schreiben können, ohne dass die Logik "von innen nach außen" ausfallen muss, wie wir es in dieser rekursiven loopFunktion tun mussten .

Hugomg
quelle
Die Rückrufhölle kann also nur in einer asynchronen Einstellung auftreten? Wenn mein Code vollständig synchron ist (dh keine Parallelität), kann "Rückrufhölle" nicht auftreten, wenn ich Ihre Antwort richtig verstehe. Ist das richtig?
Jhegedus
Die Callback-Hölle hat mehr damit zu tun, wie ärgerlich es ist, im Continuation-Passing-Stil zu codieren. Theoretisch könnten Sie auch für ein reguläres Programm alle Ihre Funktionen im CPS-Stil neu schreiben (der Wikipedia-Artikel enthält einige Beispiele), aber aus gutem Grund tun dies die meisten Leute nicht. Normalerweise verwenden wir den Continuation-Passing-Stil nur, wenn wir dazu gezwungen sind, was bei der asynchronen Javascript-Programmierung der Fall ist.
Hugomg
Übrigens habe ich nach den reaktiven Erweiterungen gegoogelt und habe den Eindruck, dass sie eher einer Promise-Bibliothek und nicht einer Spracherweiterung ähneln, die asynchrone Syntax einführt. Versprechen helfen bei der Verschachtelung von Rückrufen und bei der Ausnahmebehandlung, sind jedoch nicht so ordentlich wie die Syntaxerweiterungen. Die for-Schleife ist für den Code immer noch ärgerlich und Sie müssen den Code immer noch vom synchronen Stil in den Versprechungsstil übersetzen.
Hugomg
1
Ich sollte klarstellen, wie RX im Allgemeinen einen besseren Job macht. RX ist deklarativ. Sie können festlegen, wie das Programm auf Ereignisse reagiert, wenn diese später auftreten, ohne dass dies Auswirkungen auf eine andere Programmlogik hat. Auf diese Weise können Sie den Hauptschleifencode vom Ereignisbehandlungscode trennen. Sie können problemlos Details wie die asynchrone Ereignisreihenfolge verarbeiten, die bei Verwendung von Statusvariablen ein Albtraum sind. Ich fand, dass RX die sauberste Implementierung war, um eine neue Netzwerkanforderung auszuführen, nachdem 3 Netzwerkantworten zurückgegeben wurden, oder um die gesamte Kette fehlerhaft zu behandeln, wenn eine nicht zurückgegeben wird. Dann kann es sich selbst zurücksetzen und auf die gleichen 3 Ereignisse warten.
Colintheshots
Noch ein verwandter Kommentar: RX ist im Grunde die Fortsetzungsmonade, die sich auf CPS bezieht, wenn ich mich nicht irre. Dies könnte auch erklären, wie / warum RX für das Rückruf- / Höllenproblem gut ist.
Jhegedus
30

Beantworten Sie einfach die Frage: Können Sie bitte auch zeigen, wie RX das "Callback Hell Problem" in diesem einfachen Beispiel löst?

Die Magie ist flatMap. Wir können den folgenden Code in Rx für das Beispiel von @ hugomg schreiben:

def getData() = Observable[X]
getData().flatMap(x -> Observable[Y])
         .flatMap(y -> Observable[Z])
         .map(z -> ...)...

Es ist, als würden Sie einige synchrone FP-Codes schreiben, aber tatsächlich können Sie sie durch asynchron machen Scheduler.

zsxwing
quelle
26

Um die Frage, wie Rx löst Rückruf Hölle :

Lassen Sie uns zunächst noch einmal die Rückrufhölle beschreiben.

Stellen Sie sich einen Fall vor, in dem wir http ausführen müssen, um drei Ressourcen zu erhalten - Person, Planet und Galaxie. Unser Ziel ist es, die Galaxie zu finden, in der die Person lebt. Zuerst müssen wir die Person bekommen, dann den Planeten, dann die Galaxie. Das sind drei Rückrufe für drei asynchrone Operationen.

getPerson(person => { 
   getPlanet(person, (planet) => {
       getGalaxy(planet, (galaxy) => {
           console.log(galaxy);
       });
   });
});

Jeder Rückruf ist verschachtelt. Jeder innere Rückruf ist von seinem übergeordneten Element abhängig. Dies führt zum "Pyramid of Doom" -Stil der Rückrufhölle . Der Code sieht aus wie ein> Zeichen.

Um dies in RxJs zu lösen, könnten Sie so etwas tun:

getPerson()
  .map(person => getPlanet(person))
  .map(planet => getGalaxy(planet))
  .mergeAll()
  .subscribe(galaxy => console.log(galaxy));

Mit dem mergeMapAKA- flatMapOperator können Sie es prägnanter gestalten:

getPerson()
  .mergeMap(person => getPlanet(person))
  .mergeMap(planet => getGalaxy(planet))
  .subscribe(galaxy => console.log(galaxy));

Wie Sie sehen können, ist der Code abgeflacht und enthält eine einzelne Kette von Methodenaufrufen. Wir haben keine "Pyramide des Untergangs".

Daher wird die Rückrufhölle vermieden.

Falls Sie sich gefragt haben, sind Versprechen ein weiterer Weg, um die Hölle des Rückrufs zu vermeiden, aber Versprechen sind eifrig , nicht faul wie Observablen und (im Allgemeinen) können Sie sie nicht so einfach stornieren.

Ghostypants
quelle
Ich bin kein JS-Entwickler, aber das ist eine einfache Erklärung
Omar Beshary
15

Callback Hell ist jeder Code, bei dem die Verwendung von Funktionsrückrufen in asynchronem Code unklar oder schwer zu befolgen ist. Wenn es mehr als eine Indirektionsebene gibt, kann es im Allgemeinen schwieriger werden, Code mithilfe von Rückrufen zu folgen, schwieriger umzugestalten und schwieriger zu testen. Ein Codegeruch besteht aus mehreren Einrückungsstufen, da mehrere Ebenen von Funktionsliteralen übergeben werden.

Dies geschieht häufig, wenn das Verhalten Abhängigkeiten aufweist, dh wenn A vor B vor C auftreten muss. Dann erhalten Sie Code wie folgt:

a({
    parameter : someParameter,
    callback : function() {
        b({
             parameter : someOtherParameter,
             callback : function({
                 c(yetAnotherParameter)
        })
    }
});

Wenn Ihr Code viele Verhaltensabhängigkeiten aufweist, kann dies schnell problematisch werden. Besonders wenn es verzweigt ...

a({
    parameter : someParameter,
    callback : function(status) {
        if (status == states.SUCCESS) {
          b(function(status) {
              if (status == states.SUCCESS) {
                 c(function(status){
                     if (status == states.SUCCESS) {
                         // Not an exaggeration. I have seen
                         // code that looks like this regularly.
                     }
                 });
              }
          });
        } elseif (status == states.PENDING {
          ...
        }
    }
});

Das geht nicht. Wie können wir asynchronen Code in einer bestimmten Reihenfolge ausführen lassen, ohne all diese Rückrufe weitergeben zu müssen?

RX ist die Abkürzung für "Reactive Extensions". Ich habe es nicht benutzt, aber Googeln schlägt vor, dass es ein ereignisbasiertes Framework ist, was Sinn macht. Ereignisse sind ein gängiges Muster, mit dem Code in der richtigen Reihenfolge ausgeführt wird, ohne dass eine spröde Kopplung entsteht . Sie können C dazu bringen, das Ereignis 'bFinished' anzuhören, was erst geschieht, nachdem B als 'aFinished' bezeichnet wird. Sie können dann problemlos zusätzliche Schritte hinzufügen oder diese Art von Verhalten erweitern und auf einfache Weise testen, ob Ihr Code in der richtigen Reihenfolge ausgeführt wird, indem Sie lediglich Ereignisse in Ihrem Testfall senden.

Jimmy Breck-McKye
quelle
1

Rückruf Hölle bedeutet, dass Sie sich innerhalb eines Rückrufs innerhalb eines anderen Rückrufs befinden und es zum n-ten Anruf geht, bis Ihre Bedürfnisse nicht erfüllt sind.

Lassen Sie uns anhand eines Beispiels für einen gefälschten Ajax-Aufruf mithilfe der festgelegten Zeitüberschreitungs-API verstehen, dass wir eine Rezept-API haben und alle Rezepte herunterladen müssen.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Im obigen Beispiel wird nach 1,5 Sekunden, wenn der Timer innerhalb des Rückrufcodes abläuft, ausgeführt. Mit anderen Worten, durch unseren gefälschten Ajax-Aufruf werden alle Rezepte vom Server heruntergeladen. Jetzt müssen wir bestimmte Rezeptdaten herunterladen.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
                setTimeout(id=>{
                    const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                    console.log(`${id}: ${recipe.title}`);
                }, 1500, recipeId[2])
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Um bestimmte Rezeptdaten herunterzuladen, haben wir Code in unseren ersten Rückruf geschrieben und die Rezept-ID übergeben.

Angenommen, wir müssen alle Rezepte desselben Herausgebers des Rezepts herunterladen, dessen ID 7638 lautet.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
                setTimeout(id=>{
                    const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                    console.log(`${id}: ${recipe.title}`);
                    setTimeout(publisher=>{
                        const recipe2 = {title:'Fresh Apple Pie', publisher:'Suru'};
                        console.log(recipe2);
                    }, 1500, recipe.publisher);
                }, 1500, recipeId[2])
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Um unsere Anforderungen zu erfüllen, dh alle Rezepte des Herausgebernamens suru herunterzuladen, haben wir in unserem zweiten Rückruf Code geschrieben. Es ist klar, dass wir eine Rückrufkette geschrieben haben, die Rückrufhölle heißt.

Wenn Sie die Hölle des Rückrufs vermeiden möchten, können Sie Promise verwenden, die js es6-Funktion. Jedes Versprechen erhält einen Rückruf, der aufgerufen wird, wenn ein Versprechen vollständig erfüllt ist. Versprechen Rückruf hat zwei Optionen, entweder es wird gelöst oder abgelehnt. Angenommen, Ihr API-Aufruf ist erfolgreich. Sie können die Auflösung aufrufen und Daten durch die Auflösung übergeben . Sie können diese Daten mit then () abrufen . Wenn Ihre API jedoch fehlgeschlagen ist, können Sie Reject verwenden. Verwenden Sie catch , um den Fehler abzufangen. Denken Sie daran, ein Versprechen immer dann für die Lösung zu verwenden und für die Ablehnung zu fangen

Lassen Sie uns das vorherige Problem der Rückrufhölle mit einem Versprechen lösen.

<body>
    <script>

        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        getIds.then(IDs=>{
            console.log(IDs);
        }).catch(error=>{
            console.log(error);
        });
    </script>
</body>

Laden Sie jetzt ein bestimmtes Rezept herunter:

<body>
    <script>
        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        const getRecipe = recID => {
            return new Promise((resolve, reject)=>{
                setTimeout(id => {
                    const downloadSuccessfull = true;
                    if (downloadSuccessfull){
                        const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                        resolve(`${id}: ${recipe.title}`);
                    }else{
                        reject(`${id}: recipe download failed 404`);
                    }

                }, 1500, recID)
            })
        }
        getIds.then(IDs=>{
            console.log(IDs);
            return getRecipe(IDs[2]);
        }).
        then(recipe =>{
            console.log(recipe);
        })
        .catch(error=>{
            console.log(error);
        });
    </script>
</body>

Jetzt können wir eine andere Methode schreiben, die allRecipeOfAPublisher aufruft, wie getRecipe, die ebenfalls ein Versprechen zurückgibt, und wir können eine weitere then () schreiben, um ein Auflösungsversprechen für allRecipeOfAPublisher zu erhalten. Ich hoffe, dass Sie dies an dieser Stelle selbst tun können.

Wir haben also gelernt, wie man Versprechen konstruiert und konsumiert. Lassen Sie uns nun das Konsumieren von Versprechen vereinfachen, indem wir async / await verwenden, das in es8 eingeführt wird.

<body>
    <script>

        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        const getRecipe = recID => {
            return new Promise((resolve, reject)=>{
                setTimeout(id => {
                    const downloadSuccessfull = true;
                    if (downloadSuccessfull){
                        const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                        resolve(`${id}: ${recipe.title}`);
                    }else{
                        reject(`${id}: recipe download failed 404`);
                    }

                }, 1500, recID)
            })
        }

        async function getRecipesAw(){
            const IDs = await getIds;
            console.log(IDs);
            const recipe = await getRecipe(IDs[2]);
            console.log(recipe);
        }

        getRecipesAw();
    </script>
</body>

Im obigen Beispiel haben wir eine asynchrone Funktion verwendet, da sie im Hintergrund ausgeführt wird. Innerhalb der asynchronen Funktion haben wir das Schlüsselwort await vor jeder Methode verwendet, die zurückgibt oder ein Versprechen ist, weil wir an dieser Position warten müssen, bis dieses Versprechen erfüllt ist, mit anderen Worten in der Balgcodes, bis getIds abgeschlossen ist, aufgelöst oder Programm abgelehnt, führen die Ausführung von Codes unter dieser Zeile nicht mehr aus, wenn IDs zurückgegeben werden. Dann haben wir die Funktion getRecipe () erneut mit einer ID aufgerufen und mit dem Schlüsselwort await gewartet, bis Daten zurückgegeben wurden. So haben wir uns endlich von der Rückrufhölle erholt.

  async function getRecipesAw(){
            const IDs = await getIds;
            console.log(IDs);
            const recipe = await getRecipe(IDs[2]);
            console.log(recipe);
        }

Um wait verwenden zu können, benötigen wir eine asynchrone Funktion. Wir können ein Versprechen zurückgeben. Verwenden Sie es dann, um das Versprechen zu lösen, und kath, um das Versprechen abzulehnen

aus dem obigen Beispiel:

 async function getRecipesAw(){
            const IDs = await getIds;
            const recipe = await getRecipe(IDs[2]);
            return recipe;
        }

        getRecipesAw().then(result=>{
            console.log(result);
        }).catch(error=>{
            console.log(error);
        });
Herr
quelle
0

Eine Möglichkeit, Callback Hell zu vermeiden, ist die Verwendung von FRP, einer "erweiterten Version" von RX.

Ich habe kürzlich angefangen, FRP zu verwenden, weil ich eine gute Implementierung namens FRP gefunden habe Sodium( http://sodium.nz/). ).

Ein typischer Code sieht folgendermaßen aus (Scala.js):

def render: Unit => VdomElement = { _ =>
  <.div(
    <.hr,
    <.h2("Note Selector"),
    <.hr,
    <.br,
    noteSelectorTable.comp(),
    NoteCreatorWidget().createNewNoteButton.comp(),
    NoteEditorWidget(selectedNote.updates()).comp(),
    <.hr,
    <.br
  )
}

selectedNote.updates()ist ein Streamwas feuert, wenn selectedNode(was a ist Cell) sich ändert, dasNodeEditorWidget dann entsprechend aktualisiert.

Also, je nach Inhalt der selectedNode Cell, die aktuell bearbeitetNote ändert sich also .

Dieser Code vermeidet Callback-s vollständig, fast, Cacllback-s werden auf die "äußere Schicht" / "Oberfläche" der App verschoben, wo die Statusbehandlungslogik mit der Außenwelt verbunden ist. Es sind keine Rückrufe erforderlich, um Daten innerhalb der internen Zustandsbehandlungslogik (die eine Zustandsmaschine implementiert) weiterzugeben.

Der vollständige Quellcode ist hier

Das obige Codefragment entspricht dem folgenden einfachen Beispiel zum Erstellen / Anzeigen / Aktualisieren:

Geben Sie hier die Bildbeschreibung ein

Dieser Code sendet auch Aktualisierungen an den Server, sodass Änderungen an den aktualisierten Entitäten automatisch auf dem Server gespeichert werden.

Die gesamte Ereignisbehandlung wird mit Streams und Cells erledigt. Dies sind FRP-Konzepte. Rückrufe werden nur benötigt, wenn die FRP-Logik mit der Außenwelt verbunden ist, z. B. Benutzereingaben, Bearbeiten von Text, Drücken einer Taste, AJAX-Aufruf kehrt zurück.

Der Datenfluss wird explizit deklarativ unter Verwendung von FRP (implementiert von der Sodium-Bibliothek) beschrieben, sodass zur Beschreibung des Datenflusses keine Ereignisbehandlungs- / Rückruflogik erforderlich ist.

FRP (eine "strengere" Version von RX) ist eine Möglichkeit, ein Datenflussdiagramm zu beschreiben, das Knoten enthalten kann, die Status enthalten. Ereignisse lösen Statusänderungen in dem Status aus, der Knoten enthält ( Cells genannt).

Natrium ist eine FRP-Bibliothek höherer Ordnung, was bedeutet, dass mit dem flatMap/ switchprimitiven das Datenflussdiagramm zur Laufzeit neu angeordnet werden kann.

Ich empfehle, einen Blick in das Natrium-Buch zu werfen. Es erklärt ausführlich, wie FRP alle Rückrufe beseitigt, die für die Beschreibung der Datenflusslogik, die mit der Aktualisierung des Anwendungsstatus als Reaktion auf einige externe Stimuli zu tun hat, nicht unbedingt erforderlich sind.

Bei Verwendung von FRP müssen nur die Rückrufe beibehalten werden, die die Interaktion mit der Außenwelt beschreiben. Mit anderen Worten, der Datenfluss wird funktional / deklarativ beschrieben, wenn ein FRP-Framework (z. B. Sodium) oder ein "FRP-ähnliches" Framework (z. B. RX) verwendet wird.

Natrium ist auch für Javascript / Typescript erhältlich.

jhegedus
quelle
-3

Wenn Sie keine Kenntnisse über Rückruf und Rückruf zur Hölle haben, gibt es kein Problem. Das erste ist, dass Sie zurückrufen und die Hölle zurückrufen. Zum Beispiel: Der Rückruf zur Hölle ist wie ein Rückruf einer Klasse in einer Klasse. Wie Sie gehört haben über das in C, C ++ verschachtelte. Verschachtelt Bedeutet, dass eine Klasse in einer anderen Klasse.

Raghav Arora
quelle
Die Antwort ist hilfreicher, wenn sie ein Code-Snippet enthält, um zu zeigen, was "Callback Hell" ist, und dasselbe Code-Snippet mit Rx, nachdem "Callback Hell" entfernt wurde
Rafa
-4

Verwenden Sie jazz.js https://github.com/Javanile/Jazz.js

es vereinfacht sich wie folgt:

    // sequentielle Task verkettet ausführen
    jj.script ([
        // erste Aufgabe
        Funktion (weiter) {
            // am Ende dieses Prozesses 'next' zeigt auf die zweite Aufgabe und führt sie aus 
            callAsyncProcess1 (next);
        },
      // zweite Aufgabe
      Funktion (weiter) {
        // am Ende dieses Prozesses 'next' zeigt auf die dritte Aufgabe und führt sie aus 
        callAsyncProcess2 (next);
      },
      // Dreißig Aufgabe
      Funktion (weiter) {
        // am Ende dieses Prozesses 'nächster' Punkt auf (falls vorhanden) 
        callAsyncProcess3 (next);
      },
    ]);

Cicciodarkast
quelle
Betrachten Sie ultra-kompakt wie diese github.com/Javanile/Jazz.js/wiki/Script-showcase
cicciodarkast