Appels asynchrones et récursivité avec Node.js

Je cherche à exécuter un rappel lors de l'achèvement complet d'une fonction récursive qui peut durer pour une durée indéterminée. Je me heurte à des problèmes asynchrones et j'ai espéré obtenir de l'aide ici. Le code, en utilisant le module de request , est le suivant:

 var start = function(callback) { request.get({ url: 'aaa.com' }, function (error, response, body) { var startingPlace = JSON.parse(body).id; recurse(startingPlace, callback); }); }; var recurse = function(startingPlace, callback) { request.get({ url: 'bbb' }, function(error, response, body) { // store body somewhere outside these funtions // make second request request.get({ url: 'ccc' }, function(error, response, body) { var anArray = JSON.parse(body).stuff; if (anArray) { anArray.forEach(function(thing) { request.get({ url: 'ddd' }, function(error, response, body) { var nextPlace = JSON.parse(body).place; recurse(nextPlace); }); }) } }); }); callback(); }; start(function() { // calls final function to print out results from storage that gets updated each recursive call finalFunction(); }); 

Il semble qu'une fois que mon code dépasse la boucle for dans les requêtes imbriquées, il continue de la demande et finit l'appel de la fonction initiale pendant que les appels récursifs continuent. Je veux qu'il ne termine pas l'itération de haut niveau jusqu'à ce que tous les appels récursifs imbriqués aient été complétés (ce que je n'ai aucun moyen de savoir combien il existe).

Toute aide est grandement appréciée!

Dans votre exemple, vous n'avez pas d'appels récursifs. Si je comprends correctement, vous voulez dire que recurse(point, otherFunc); Est le début d'un appel récursif.

Ensuite, revenez simplement à la définition de l'appel récursif (que vous n'avez pas montré dans votre publication) et faites ceci (ajoutez un troisième argument pour appeler une fonction de rappel en fin de récursion; l'appelant le passera comme paramètre ):

 function recurse(startingPlace, otherFunc, callback_one) { // code you may have ... if (your_terminating_criterion === true) { return callback_one(val); // where val is potentially some value you want to return (or a json object with results) } // more code you may have } 

Ensuite, dans le code original que vous avez posté, faites plutôt cet appel (dans la partie la plus intégrée):

 recurse(startingPlace, otherFunc, function (results) { // results is now a variable with the data returned at the end of recursion console.log ("Recursion finished with results " + results); callback(); // the callback that you wanted to call right from the beginning }); 

Passez juste du temps et essayez de comprendre mon explication. Lorsque vous comprenez, vous connaîtrez le noeud. C'est la philosophie du noeud dans un post. J'espère que c'est clair. Votre premier exemple devrait ressembler à ceci:

 var start = function(callback) { request.get({ url: 'aaa.com' }, function (error, response, body) { var startingPlace = JSON.parse(body).id; recurse(startingPlace, otherFunc, function (results) { console.log ("Recursion finished with results " + results); callback(); }); }); }; 

Vous trouverez ci-dessous des informations supplémentaires si vous êtes intéressé. Sinon, vous êtes configuré avec ce qui précède.

Typiquement, dans node.js, les gens renvoient également une valeur d'erreur, de sorte que l'appelant sait si la fonction appelée a terminé avec succès. Il n'y a pas de grand mystère ici. Au lieu de revenir sur les results gens appellent le formulaire

 return callback_one(null, val); 

Ensuite, dans l'autre fonction, vous pouvez avoir:

 recurse(startingPlace, otherFunc, function (recError, results) { if (recErr) { // treat the error from recursion return callback(); // important: use return, otherwise you will keep on executing whatever is there after the if part when the callback ends ;) } // No problems/errors console.log ("Recursion finished with results " + results); callback(); // writing down `return callback();` is not a bad habit when you want to stop execution there and actually call the callback() }); 

Mise à jour avec ma suggestion

Ceci est ma suggestion pour la fonction récursive, mais avant cela, il semble que vous devez définir votre propre get :

 function myGet (a, callback) { request.get(a, function (error, response, body) { var nextPlace = JSON.parse(body).place; return callback(null, nextPlace); // null for no errors, and return the nextPlace to async }); } var recurse = function(startingPlace, callback2) { request.get({ url: 'bbb' }, function(error1, response1, body1) { // store body somewhere outside these funtions // make second request request.get({ url: 'ccc' }, function(error2, response2, body2) { var anArray = JSON.parse(body2).stuff; if (anArray) { // The function that you want to call for each element of the array is `get`. // So, prepare these calls, but you also need to pass different arguments // and this is where `bind` comes into the picture and the link that I gave earlier. var theParallelCalls = []; for (var i = 0; i < anArray.length; i++) { theParallelCalls.push(myGet.bind(null, {url: 'ddd'})); // Here, during the execution, parallel will pass its own callback as third argument of `myGet`; this is why we have callback and callback2 in the code } // Now perform the parallel calls: async.parallel(theParallelCalls, function (error3, results) { // All the parallel calls have returned for (var i = 0; i < results.length; i++) { var nextPlace = results[i]; recurse(nextPlace, callback2); } }); } else { return callback2(null); } }); }); }; 

Notez que je suppose que la requête get pour 'bbb' est toujours suivie d'une requête get pour 'ccc'. En d'autres termes, vous n'avez pas caché un point de retour pour les appels récursifs où vous avez les commentaires.

En règle générale, lorsque vous écrivez une fonction récursive, il fera quelque chose et que vous vous appelez ou renvoyez.

Vous devez définir le callback dans la portée de la fonction récursive (c.-à-d. recurse au lieu du start ), et vous devez l'appeler au point où vous retournez normalement.

Donc, un exemple hypothétique ressemblerait à quelque chose comme:

 get_all_pages(callback, page) { page = page || 1; request.get({ url: "http://example.com/getPage.php", data: { page_number: 1 }, success: function (data) { if (data.is_last_page) { // We are at the end so we call the callback callback(page); } else { // We are not at the end so we recurse get_all_pages(callback, page + 1); } } } } function show_page_count(data) { alert(data); } get_all_pages(show_page_count); 

Je pense que vous pourriez trouver caolan / async utile. Regardez surtout l' async.waterfall . Cela vous permettra de répercuter les résultats d'un retour d'un autre et, lorsque cela est fait, faire quelque chose avec les résultats.

Exemple:

 async.waterfall([ function(cb) { request.get({ url: 'aaa.com' }, function(err, res, body) { if(err) { return cb(err); } cb(null, JSON.parse(body).id); }); }, function(id, cb) { // do that otherFunc now // ... cb(); // remember to pass result here } ], function (err, result) { // do something with possible error and result now }); 

Si votre fonction récursive est synchrone, appelez simplement le rappel sur la ligne suivante:

 var start = function(callback) { request.get({ url: 'aaa.com' }, function (error, response, body) { var startingPlace = JSON.parse(body).id; recurse(startingPlace, otherFunc); // Call output function AFTER recursion has completed callback(); }); }; 

Sinon, vous devez garder une référence au rappel dans votre fonction récursive.

Passez le rappel comme un argument à la fonction et appelez-le chaque fois qu'il est terminé.

 var start = function(callback) { request.get({ url: 'aaa.com' }, function (error, response, body) { var startingPlace = JSON.parse(body).id; recurse(startingPlace, otherFunc, callback); }); }; 

Créez votre code à partir de cet exemple:

 var udpate = function (callback){ //Do stuff callback(null); } function doUpdate() { update(updateDone) } function updateDone(err) { if (err) throw err; else doUpdate() } doUpdate();