1) Qu'est-ce qu'un "enfer de rappel" pour quelqu'un qui ne connaît pas javascript et node.js?
Cette autre question a quelques exemples de l'enfer des callbacks Javascript: Comment éviter l'imbrication longue de fonctions asynchrones dans Node.js
Le problème en Javascript est que la seule façon de "figer" un calcul et de faire exécuter le "reste" (de manière asynchrone) est de mettre "le reste" dans un callback.
Par exemple, disons que je veux exécuter du 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 la possibilité d'exécuter un autre code pendant que j'attends qu'ils renvoient leurs valeurs? En Javascript, le seul moyen serait de réécrire tout ce qui touche un calcul asynchrone en utilisant le style de passage de continuation :
getData(function(x){
getMoreData(x, function(y){
getMoreData(y, function(z){
...
});
});
});
Je ne pense pas avoir besoin de convaincre qui que ce soit que cette version est plus moche que la précédente. :-)
2) Quand (dans quel type de paramètres) le "problème de l'enfer du rappel" se produit-il?
Lorsque vous avez beaucoup de fonctions de rappel dans votre code! Il devient de plus en plus difficile de travailler avec eux plus vous en avez dans votre code et cela devient particulièrement difficile lorsque vous avez besoin de faire des boucles, des blocs try-catch et des choses comme ça.
Par exemple, pour autant que je sache, en JavaScript, le seul moyen d'exécuter une série de fonctions asynchrones où l'une est exécutée après les retours précédents consiste à utiliser une fonction récursive. Vous ne pouvez pas utiliser une boucle for.
// we would like to write the following
for(var i=0; i<10; i++){
doSomething(i);
}
blah();
Au lieu de cela, nous pourrions avoir besoin de finir par é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 recevons ici sur StackOverflow demandant comment faire ce genre de chose témoigne de la confusion :)
3) Pourquoi cela se produit-il?
Cela se produit parce qu'en JavaScript, la seule façon de retarder un calcul afin qu'il s'exécute après le retour de l'appel asynchrone est de placer le code retardé dans une fonction de rappel. Vous ne pouvez pas retarder le code qui a été écrit dans un style synchrone traditionnel afin de vous retrouver avec des rappels imbriqués partout.
4) Ou "l'enfer de rappel" peut-il se produire également dans une seule application threadée?
La programmation asynchrone a à voir avec la concurrence tandis qu'un seul thread a à voir avec le parallélisme. Les deux concepts ne sont en fait pas la même chose.
Vous pouvez toujours avoir du code simultané dans un seul contexte de thread. En fait, JavaScript, la reine de l'enfer des rappels, est monothread.
Quelle est la différence entre la concurrence et le parallélisme?
5) pourriez-vous s'il vous plaît montrer également comment RX résout le "problème de l'enfer des rappels" sur cet exemple simple.
Je ne sais rien de RX en particulier, mais ce problème est généralement résolu en ajoutant un support natif pour le calcul asynchrone dans le langage de programmation. Les implémentations peuvent varier et inclure: async, générateurs, coroutines et callcc.
En Python, nous pouvons implémenter cet exemple de boucle précédent avec quelque chose du genre:
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" met en pause notre boucle for jusqu'à ce que quelqu'un appelle myGen.next (). L'important est que nous pourrions toujours écrire le code en utilisant une boucle for, sans avoir besoin de retourner la logique «à l'envers» comme nous avons dû le faire dans cette loop
fonction récursive .
Répondez simplement à la question: pourriez-vous s'il vous plaît montrer également comment RX résout le "problème de l'enfer du rappel" sur cet exemple simple?
La magie est
flatMap
. Nous pouvons écrire le code suivant dans Rx pour l'exemple de @ hugomg:C'est comme si vous écriviez des codes FP synchrones, mais en fait, vous pouvez les rendre asynchrones par
Scheduler
.la source
Pour répondre à la question de savoir comment Rx résout l' enfer des rappels :
Décrivons d'abord l'enfer des rappels.
Imaginez un cas où nous devons faire http pour obtenir trois ressources - personne, planète et galaxie. Notre objectif est de trouver la galaxie dans laquelle vit la personne. Nous devons d'abord obtenir la personne, puis la planète, puis la galaxie. Cela représente trois rappels pour trois opérations asynchrones.
Chaque rappel est imbriqué. Chaque rappel interne dépend de son parent. Cela conduit au style "pyramide du destin" de l' enfer de rappel . Le code ressemble à un signe>.
Pour résoudre ce problème dans RxJs, vous pouvez faire quelque chose comme ceci:
Avec l' opérateur
mergeMap
AKAflatMap
, vous pouvez le rendre plus succinct:Comme vous pouvez le voir, le code est aplati et contient une seule chaîne d'appels de méthode. Nous n'avons pas de "pyramide de malheur".
Par conséquent, l'enfer des rappels est évité.
Au cas où vous vous poseriez la question, les promesses sont un autre moyen d'éviter l'enfer des rappels, mais les promesses sont avides , pas paresseuses comme les observables et (en général) vous ne pouvez pas les annuler aussi facilement.
la source
L'enfer des rappels est tout code où l'utilisation des rappels de fonction dans le code asynchrone devient obscure ou difficile à suivre. Généralement, lorsqu'il y a plus d'un niveau d'indirection, le code utilisant des callbacks peut devenir plus difficile à suivre, plus difficile à refactoriser et plus difficile à tester. Une odeur de code correspond à plusieurs niveaux d'indentation dus au passage de plusieurs couches de littéraux de fonction.
Cela se produit souvent lorsque le comportement a des dépendances, c'est-à-dire lorsque A doit se produire avant que B ne se produise avant C. Ensuite, vous obtenez un code comme celui-ci:
Si vous avez beaucoup de dépendances comportementales dans votre code comme celle-ci, cela peut devenir rapidement gênant. Surtout si ça se ramifie ...
Ça ne va pas. Comment pouvons-nous faire exécuter du code asynchrone dans un ordre déterminé sans avoir à passer tous ces rappels?
RX est l'abréviation de «extensions réactives». Je ne l'ai pas utilisé, mais Google suggère qu'il s'agit d'un cadre basé sur les événements, ce qui a du sens. Les événements sont un modèle courant pour 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 se produit seulement après que B est appelé à écouter 'aFinished'. Vous pouvez ensuite facilement ajouter des étapes supplémentaires ou étendre ce type de comportement et tester facilement que votre code s'exécute dans l'ordre en diffusant simplement des événements dans votre scénario de test.
la source
L'enfer de rappel signifie que vous êtes à l'intérieur d'un rappel d'un autre rappel et qu'il passe au nième appel jusqu'à ce que vos besoins ne soient pas satisfaits.
Comprenons à travers un exemple de faux appel ajax en utilisant l'API set timeout, supposons que nous ayons une API de recette, nous devons télécharger toutes les recettes.
Dans l'exemple ci-dessus, après 1,5 seconde, lorsque la minuterie expire, le code de rappel sera exécuté, en d'autres termes, grâce à notre faux appel ajax, toutes les recettes seront téléchargées depuis le serveur. Nous devons maintenant télécharger les données d'une recette particulière.
Pour télécharger une donnée de recette particulière, nous avons écrit du code à l'intérieur de notre premier rappel et passé l'ID de recette.
Disons maintenant que nous devons télécharger toutes les recettes du même éditeur de la recette dont l'ID est 7638.
Pour répondre à tous nos besoins, c'est-à-dire télécharger toutes les recettes du nom d'éditeur suru, nous avons écrit du code à l'intérieur de notre deuxième rappel. Il est clair que nous avons écrit une chaîne de callback qui s'appelle Callback Hell.
Si vous voulez éviter l'enfer des rappels, vous pouvez utiliser Promise, qui est la fonctionnalité js es6, chaque promesse prend un rappel qui est appelé lorsqu'une promesse est remplie. Le rappel de promesse a deux options: il est résolu ou rejeté. Supposons que votre appel API aboutisse, vous pouvez appeler la résolution et transmettre les données via la résolution , vous pouvez obtenir ces données en utilisant then () . Mais si votre API échoue, vous pouvez utiliser le rejet, utiliser catch pour attraper l'erreur. Rappelez - vous une promesse de toujours utiliser alors pour résoudre et prises pour rejeter
Résolvons le problème précédent de l'enfer des rappels en utilisant une promesse.
Maintenant, téléchargez une recette particulière:
Maintenant, nous pouvons écrire une autre méthode appelée allRecipeOfAPublisher comme getRecipe qui renverra également une promesse, et nous pouvons en écrire une autre then () pour recevoir la promesse de résolution pour allRecipeOfAPublisher, j'espère qu'à ce stade, vous pourrez le faire vous-même.
Nous avons donc appris à construire et à consommer des promesses, facilitons maintenant la consommation d'une promesse en utilisant async / await qui est introduit dans es8.
Dans l'exemple ci-dessus, nous avons utilisé une fonction async car elle s'exécutera en arrière-plan, à l'intérieur de la fonction async nous avons utilisé le mot-clé await avant chaque méthode qui retourne ou est une promesse car attendre sur cette position jusqu'à ce que cette promesse se réalise, en d'autres termes dans le les codes ci-dessous jusqu'à ce que getIds soit résolu ou le programme de rejet cessera d'exécuter les codes sous cette ligne lorsque les ID sont retournés, puis nous avons à nouveau appelé la fonction getRecipe () avec un identifiant et avons attendu en utilisant le mot-clé await jusqu'à ce que les données soient renvoyées. C'est ainsi que nous avons finalement récupéré de l'enfer du rappel.
Pour utiliser await, nous aurons besoin d'une fonction asynchrone, nous pouvons renvoyer une promesse, alors utilisez-la pour résoudre la promesse et cath pour rejeter la promesse
à partir de l'exemple ci-dessus:
la source
Une façon d'éviter l'enfer du rappel est d'utiliser FRP qui est une «version améliorée» de RX.
J'ai commencé à utiliser FRP récemment car j'en ai trouvé une bonne implémentation appelée
Sodium
( http://sodium.nz/ ).Un code typique ressemble à ceci (Scala.js):
selectedNote.updates()
est unStream
qui se déclenche siselectedNode
(qui est aCell
) change,NodeEditorWidget
alors se met à jour en conséquence.Ainsi, en fonction du contenu du
selectedNode
Cell
, celui actuellement éditéNote
changera.Ce code évite que les Callback-s entièrement, presque, les Cacllback-s soient poussés vers la "couche externe" / "surface" de l'application, où la logique de gestion d'état s'interface avec le monde externe. Aucun rappel n'est nécessaire pour propager les données dans la logique de gestion d'état interne (qui implémente une machine d'état).
Le code source complet est ici
L'extrait de code ci-dessus correspond à l'exemple de création / affichage / mise à jour simple suivant:
Ce code envoie également des mises à jour au serveur, de sorte que les modifications apportées aux entités mises à jour sont automatiquement enregistrées sur le serveur.
Toute la gestion des événements est prise en charge en utilisant
Stream
s etCell
s. Ce sont des concepts FRP. Les rappels ne sont nécessaires que lorsque la logique FRP s'interface avec le monde externe, comme l'entrée utilisateur, l'édition de texte, la pression sur un bouton, l'appel AJAX revient.Le flux de données est décrit explicitement, de manière déclarative à l'aide de FRP (implémenté par la bibliothèque Sodium), de sorte qu'aucune logique de gestion d'événements / de rappel n'est nécessaire pour décrire le flux de données.
FRP (qui est une version plus "stricte" de RX) est un moyen de décrire un graphe de flux de données, qui peut contenir des nœuds contenant un état. Les événements déclenchent des changements d'état dans l'état contenant des nœuds (appelés
Cell
s).Sodium est une bibliothèque FRP d'ordre supérieur, ce qui signifie que l'utilisation de la primitive
flatMap
/switch
permet de réorganiser le graphique de flux de données au moment de l'exécution.Je recommande de jeter un coup d'œil dans le livre Sodium , il explique en détail comment FRP se débarrasse de tous les rappels qui ne sont pas essentiels pour décrire la logique de flux de données qui a à voir avec la mise à jour de l'état des applications en réponse à certains stimuli externes.
En utilisant FRP, seuls les rappels doivent être conservés qui décrivent l'interaction avec le monde externe. En d'autres termes, le flux de données est décrit de manière fonctionnelle / déclarative lorsque l'on utilise un framework FRP (tel que Sodium), ou lorsque l'on utilise un framework "FRP like" (tel que RX).
Sodium est également disponible pour Javascript / Typescript.
la source
Si vous ne connaissez pas le callback et le callback de l'enfer, il n'y a pas de problème, le premier est que le rappel et le rappel de l'enfer, par exemple: le rappel de l'enfer est comme si nous pouvons stocker une classe dans une classe. à propos de celui imbriqué en langage C, C ++.Nested Signifie qu'une classe dans une autre classe.
la source
Utilisez jazz.js https://github.com/Javanile/Jazz.js
ça simplifie comme ça:
la source