Qu'est-ce que "callback hell" et comment et pourquoi RX résout-il?

peut quelqu'un donner une définition claire avec un exemple simple qui explique ce qui est un" enfer de rappel " pour quelqu'un qui ne connaît pas JavaScript et noeud.js ?

quand (dans quel type de réglages) le "problème de l'enfer du rappel" se produit-il?

pourquoi cela se produit-il?

Est "rappel de l'enfer" toujours liées à des calculs asynchrones?

ou peut "callback hell" se produire aussi dans un seul thread application?

j'ai pris le cours réactif à Coursera et Erik Meijer a dit dans un de ses cours que RX résout le problème de"l'enfer du rappel". J'ai demandé ce qu'était un "callback hell" sur le forum de Coursera mais je n'ai pas eu de réponse claire.

après avoir expliqué" callback hell "sur un exemple simple, pourriez-vous aussi montrer comment RX résout le" callback hell problem " sur cet exemple simple?

83
demandé sur ROMANIA_engineer 2014-08-02 22:18:51

5 réponses

1) Qu'est-ce qu'un "enfer de rappel" pour quelqu'un qui ne connaît pas javascript et le noeud.js ?

cette autre question a quelques exemples de rappel Javascript hell: Comment éviter la longue imbrication des fonctions asynchrones dans le noeud.js

Le problème en Javascript, c'est que la seule façon de "geler" un calcul et d'avoir le "reste" exécuter ceux-ci (de manière asynchrone) c'est que "le reste" à l'intérieur d'un rappel.

par exemple, dites que je veux lancer un code qui ressemble à ceci:

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

que se passe-t-il si maintenant je veux rendre les fonctions getData asynchrones, ce qui signifie que j'ai une chance d'exécuter un autre code pendant que j'attends qu'ils retournent leurs valeurs? Dans Javascript, la seule façon serait de réécrire tout ce qui touche un calcul asynchrone en utilisant continuation passing style :

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

Je ne pense pas que j'ai besoin de convaincre quelqu'un que cette version est plus moche que la précédente. :- )

2) Quand (dans quel type de réglages) le "problème de l'enfer du rappel" se produit-il?

Quand vous avez beaucoup de fonctions de rappel dans votre code! Il devient plus difficile de travailler avec eux le plus d'entre eux que vous avez dans votre code et il devient particulièrement mauvais quand vous avez besoin de faire des boucles, essayer-attraper des blocs et des choses comme ça.

pour exemple, pour autant que je sache, en JavaScript la seule façon d'exécuter une série de fonctions asynchrones où l'une est exécutée après les retours précédents est d'utiliser une fonction récursive. Vous ne pouvez pas utiliser un pour boucle.

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

au lieu de cela, nous pourrions avoir besoin d'écrire:

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

//ugh!

le nombre de questions que nous obtenons ici sur StackOverflow demandant comment faire ce genre de chose est un témoignage à quel point il est confus:)

3) Pourquoi il se produire ?

il se produit parce que dans JavaScript la seule façon de retarder un calcul de sorte qu'il court après les retours d'appel asynchrone est de mettre le code retardé dans une fonction de rappel. Vous ne pouvez pas retarder le code qui a été écrit dans le style synchrone traditionnel de sorte que vous finissez avec des callbacks imbriqués partout.

4) ou "callback hell" peut-il également se produire dans une seule application filetée?

la programmation Asynchrone a à voir avec concurrence alors qu'un fil simple est lié au parallélisme. Les deux concepts sont en fait pas la même chose.

vous pouvez toujours avoir du code concurrent dans un seul contexte threadé. En fait, JavaScript, la Reine de l'enfer du rappel, est un simple threaded.

Quelle est la différence entre la concurrence et le parallélisme?

5) Pourriez-vous également montrer comment RX résout le "problème de callback hell" sur ce exemple simple.

Je ne sais rien sur RX en particulier, mais habituellement ce problème est résolu en ajoutant le support natif pour le calcul asynchrone dans le langage de programmation. Cela peut très beaucoup et peut venir dans des noms différents (async, générateurs, coroutines, callcc, ...). Par exemple, en Python nous pouvons implémenter cet exemple de boucle précédente avec quelque chose comme:

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

myGen = myLoop()

Ce n'est pas le code complet, mais l'idée est que le" yield " interrompt notre boucle jusqu'à ce que quelqu'un appelle myGen.prochain.)( L'important est que nous puissions encore écrire le code en utilisant une boucle for, sans avoir besoin de désactiver la logique "inside out" comme nous devions le faire dans cette fonction récursive loop .

102
répondu hugomg 2018-09-21 03:07:59

répondez simplement à la question: Pourriez-vous s'il vous plaît aussi montrer comment RX résout le" problème de callback hell " sur cet exemple simple?

la magie est flatMap . Nous pouvons écrire le code suivant dans Rx pour l'exemple de @hugomg:

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

c'est comme si vous écriviez des codes FP synchrones, mais en fait vous pouvez les rendre asynchrones par Scheduler .

26
répondu zsxwing 2014-08-04 01:20:14

Callback hell est tout code où l'utilisation de callbacks de fonction dans le code async devient obscure ou difficile à suivre. En général, lorsqu'il y a plus d'un niveau d'indirection, le code utilisant les callbacks peut devenir plus difficile à suivre, plus difficile à remanier, et plus difficile à tester. Une odeur de code est plusieurs niveaux d'indentation en raison de passer plusieurs couches de fonction littérales.

cela se produit souvent quand le comportement a des dépendances, c.-à-d. quand A doit se produire avant B doit se produire avant C. puis vous obtenez le code comme ceci:

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

si vous avez beaucoup de dépendances comportementales dans votre code comme ceci, il peut devenir gênant rapidement. Surtout si elle se ramifie...

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

ça ne va pas. Comment Pouvons-nous faire exécuter du code asynchrone dans un ordre déterminé sans avoir à passer tous ces callbacks?

RX est l'abréviation de "reactive extensions". Je ne L'ai pas utilisé, mais Googling suggère que c'est un cadre basé sur l'événement, ce qui a du sens. Les événements sont un motif courant pour faire exécuter le code dans l'ordre sans créer de couplage fragile . Vous pouvez faire en sorte que C écoute l'événement "bFinished" qui ne se produit qu'après que B est appelé "listening to aFinished". Vous pouvez alors facilement ajouter des étapes supplémentaires ou étendre ce genre de comportement, et peut facilement tester que votre code exécute dans l'ordre en diffusant simplement des événements dans votre cas test.

13
répondu Jimmy Breck-McKye 2014-08-02 18:38:52

pour répondre À la question de savoir comment Rx résout rappel de l'enfer :

décrivons d'abord l'enfer du rappel.

Imaginez un cas où nous devions faire http pour obtenir trois ressources - personne, planète et galaxie. Notre objectif est de trouver la galaxie, la personne vit dans. Nous devons d'abord attraper la personne, puis la planète, puis la galaxie. Trois rappels pour trois opérations asynchrones.

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

chaque appel est imbriqué. Chaque callback interne dépend de son parent. Cela conduit à la "pyramide de la mort" style de rappel de l'enfer . Le code ressemble à un signe>.

pour résoudre cela dans RxJs vous pourriez faire quelque chose comme cela:

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

avec l'opérateur mergeMap alias flatMap vous pourriez le rendre plus succinct:

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

comme vous pouvez le voir, le code est aplati et contient une chaîne unique d'appels de méthode. Nous n'avons pas de "pyramide de la mort".

donc, l'enfer du rappel est évité.

Dans le cas où vous vous demandez, promesses sont une autre façon d'éviter de rappel de l'enfer, mais les promesses sont désireux , pas paresseux comme observables et (en général) vous ne pouvez pas annuler comme facilement.

13
répondu ghostypants 2018-07-18 07:33:43

utilisez jazz.js https://github.com/Javanile/Jazz.js

il simplifier comme ceci:


    // run sequential task chained
    jj.script([
        // first task
        function(next) {
            // at end of this process 'next' point to second task and run it 
            callAsyncProcess1(next);
        },
      // second task
      function(next) {
        // at end of this process 'next' point to thirt task and run it 
        callAsyncProcess2(next);
      },
      // thirt task
      function(next) {
        // at end of this process 'next' point to (if have) 
        callAsyncProcess3(next);
      },
    ]);

-3
répondu cicciodarkast 2016-10-03 09:08:13