Comment puis-je arrêter les fuites de mémoire avec des promesses récursives javascript?

Comment créer une chaîne récursive de promesses de Javascript avec la bibliothèque Q ? Le code suivant ne parvient pas à être terminé dans Chrome:

<html> <script src="q.js" type="text/javascript"></script> <script type="text/javascript"> //Don't keep track of a promises stack for debugging //Reduces memory usage when recursing promises Q.longStackJumpLimit = 0; function do_stuff(count) { if (count==1000000) { return; } if (count%10000 == 0){ console.log( count ); } return Q.delay(1).then(function() { return do_stuff(count+1); }); } do_stuff(0) .then(function() { console.log("Done"); }); </script> </html> 

Cela n'empêchera pas le débordement car les promesses de rupture de la pile, mais cela va perdre de la mémoire. Si vous exécutez ce même code dans node.js, vous obtiendrez une erreur qui se lit comme suit:

 FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory 

Ce qui se passe ici, c'est qu'une chaîne très longue de promesses imbriquées est en train d'être créée, chacune attendant la prochaine. Ce que vous devez faire, c'est trouver un moyen d'aplanir cette chaîne afin qu'il n'y ait qu'une seule promesse de niveau supérieur qui soit renvoyée, en attendant la promesse intérieure qui représente actuellement un vrai travail.

Briser la chaîne

La solution la plus simple est de construire une nouvelle promesse au niveau supérieur et de l'utiliser pour briser la récurrence:

 var Promise = require('promise'); function delay(timeout) { return new Promise(function (resolve) { setTimeout(resolve, timeout); }); } function do_stuff(count) { return new Promise(function (resolve, reject) { function doStuffRecursion(count) { if (count==1000000) { return resolve(); } if (count%10000 == 0){ console.log( count ); } delay(1).then(function() { doStuffRecursion(count+1); }).done(null, reject); } doStuffRecursion(count); }); } do_stuff(0).then(function() { console.log("Done"); }); 

Bien que cette solution soit quelque peu inelegante, vous pouvez être certain qu'elle fonctionnera dans toutes les implémentations prometteuses.

Alors / promesse maintenant prend en charge la récurrence de la queue

Certaines promesses d'implémentations (par exemple, promesse de npm, que vous pouvez télécharger en tant que bibliothèque autonome à partir de https://www.promisejs.org/ ) détectent correctement cette affaire et réduisent la chaîne de promesses en une seule promesse. Cela fonctionne si vous ne respectez pas la promesse retournée par la fonction de niveau supérieur (c.-à-d. .then le immédiatement, ne le gardez pas).

Bien:

 var Promise = require('promise'); function delay(timeout) { return new Promise(function (resolve) { setTimeout(resolve, timeout); }); } function do_stuff(count) { if (count==1000000) { return; } if (count%10000 == 0){ console.log( count ); } return delay(1).then(function() { return do_stuff(count+1); }); } do_stuff(0).then(function() { console.log("Done"); }); 

Mal:

 var Promise = require('promise'); function delay(timeout) { return new Promise(function (resolve) { setTimeout(resolve, timeout); }); } function do_stuff(count) { if (count==1000000) { return; } if (count%10000 == 0){ console.log( count ); } return delay(1).then(function() { return do_stuff(count+1); }); } var thisReferenceWillPreventGarbageCollection = do_stuff(0); thisReferenceWillPreventGarbageCollection.then(function() { console.log("Done"); }); 

Malheureusement, aucune des implémentations promises n'ont cette optimisation et aucun n'a l'intention de l'implémenter.

Voici la mise en œuvre la plus simple de ce que vous essayez de faire, si cela fonctionne, il y a un problème avec la bibliothèque q, sinon il existe des problèmes de javascript profonds:

 <html> <script type="text/javascript"> function do_stuff(count) { if (count==1000000) { return done(); } if (count%1000 == 0){ console.log( count ); } return setTimeout(function() { do_stuff(count+1); }, 0); } do_stuff(0); function done() { console.log("Done"); }; </script> </html>