Gérer la pyramide de rappel node.js

9

Je viens de commencer à utiliser node, et une chose que j'ai rapidement remarquée est la rapidité avec laquelle les rappels peuvent atteindre un niveau stupide d'indentation:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

Le guide de style officiel dit de mettre chaque rappel dans une fonction distincte, mais cela semble trop restrictif sur l'utilisation des fermetures et de rendre disponible un seul objet déclaré au niveau supérieur plusieurs couches, car l'objet doit être traversé par tous les rappels intermédiaires.

Est-il correct d'utiliser l'étendue des fonctions pour aider ici? Mettez toutes les fonctions de rappel qui ont besoin d'accéder à un objet global-ish à l'intérieur d'une fonction qui déclare cet objet, donc il va dans une fermeture?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

et ainsi de suite pour plusieurs couches supplémentaires ...

Ou existe-t-il des cadres, etc. pour aider à réduire les niveaux d'indentation sans déclarer une fonction nommée pour chaque rappel unique? Comment gérez-vous la pyramide des rappels?

thecoop
la source
2
Notez un problème supplémentaire avec les fermetures - dans JS, la fermeture capture tout le contexte parent (dans d'autres langues, elle ne capture que les variables utilisées ou celles spécifiquement demandées par l'utilisateur), provoquant de belles fuites de mémoire si la hiérarchie de rappel est suffisamment profonde et si, par exemple, le rappel est conservé quelque part.
Eugene

Réponses:

7

Les promesses offrent une séparation nette des problèmes entre le comportement asynchrone et l'interface afin que les fonctions asynchrones puissent être appelées sans rappels et que l'interaction avec les rappels puisse être effectuée sur l'interface de promesse générique.

Il existe plusieurs implémentations de "promesses":


Par exemple, vous pouvez réécrire ces rappels imbriqués

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

comme

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

Au lieu du rappel dans le rappel, a(b(c()))vous enchaînez le ".then" a().then(b()).then(c()).


Une introduction ici: http://howtonode.org/promises

Fabien Sa
la source
Pourriez-vous expliquer davantage ce que font ces ressources et pourquoi recommandez-vous ces réponses pour répondre à la question posée? Les «réponses de lien uniquement» ne sont pas tout à fait les bienvenues à Stack Exchange
gnat
1
D'accord désolé. J'ai ajouté un exemple et plus d'informations.
Fabien Sa
3

Comme alternative aux promesses, vous devriez jeter un œil au yieldmot - clé en combinaison avec les fonctions de générateur qui seront introduites dans EcmaScript 6. Les deux sont disponibles aujourd'hui dans les builds Node.js 0.11.x, mais vous devez également spécifier l' --harmonyindicateur lors de l'exécution de Node .js:

$ node --harmony app.js

L'utilisation de ces constructions et d'une bibliothèque telle que TJ Holowaychuk's co vous permet d'écrire du code asynchrone dans un style qui ressemble à du code synchrone, bien qu'il fonctionne toujours de manière asynchrone. Fondamentalement, ces éléments implémentent ensemble la prise en charge de routine pour Node.js.

Fondamentalement, ce que vous devez faire est d'écrire une fonction de générateur pour le code qui exécute des trucs asynchrones, appelez-y les trucs asynchrones, mais préfixez-la avec le yieldmot - clé. Donc, à la fin, votre code ressemble à:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Pour exécuter cette fonction de générateur, vous avez besoin d'une bibliothèque telle que le co mentionné ci-dessus. L'appel ressemble alors à:

co(run);

Ou, pour le mettre en ligne:

co(function * () {
  // ...
});

Veuillez noter qu'à partir des fonctions du générateur, vous pouvez appeler d'autres fonctions du générateur, tout ce que vous avez à faire est de les préfixer à yieldnouveau.

Pour une introduction à ce sujet, recherchez sur Google des termes tels que yield generators es6 async nodejset vous devriez trouver des tonnes d'informations. Il faut un certain temps pour s'y habituer, mais une fois que vous l'obtenez, vous ne voulez plus jamais y retourner.

Veuillez noter que cela ne vous fournit pas seulement une syntaxe plus agréable pour appeler des fonctions, il vous permet également d'utiliser les éléments de logique de flux de contrôle habituels (synchrones), tels que les forboucles ou try/ catch. Plus besoin de jouer avec beaucoup de rappels et toutes ces choses.

Bonne chance et amusez-vous bien :-)!

Golo Roden
la source
0

Vous avez maintenant le package asyncawait , avec une syntaxe très proche de ce qui devrait être le futur support natif de await& asyncin Node.

Fondamentalement, il vous permet d' écrire du code asynchrone de façon synchrone , réduisant considérablement les niveaux de LOC et d'indentation, avec le compromis d'une légère perte de performances (les numéros du propriétaire du package sont 79% plus rapides que les rappels bruts), espérons-le réduit lorsque le support natif sera disponible.

Encore un bon choix pour sortir de l'enfer de rappel / pyramide du cauchemar malheureux OMI, lorsque les performances ne sont pas la principale préoccupation et où le style d'écriture synchrone convient mieux aux besoins de votre projet.

Exemple de base du package doc:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Frosty Z
la source