promiseがスタックを壊すため、これはスタックオーバーフローにはなりませんが、メモリリークが発生します。node.jsでこれと同じコードを実行すると、次のようなエラーが発生します。
FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
ここで起こっていることは、ネストされたプロミスの非常に長いチェーンが作成されており、それぞれが次のプロミスを待っているということです。あなたがする必要があるのは、そのチェーンをフラット化する方法を見つけて、返されるトップレベルの約束が1つだけになるようにし、現在実際の作業を表している最も内側の約束を待つことです。
チェーンを壊す
最も簡単な解決策は、トップレベルで新しいpromiseを構築し、それを使用して再帰を破ることです。
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");
});
このソリューションはややエレガントではありませんが、すべてのPromise実装で確実に機能します。
then/promiseが末尾再帰をサポートするようになりました
一部のpromise実装(たとえば、 https://www.promisejs.org/からスタンドアロンライブラリとしてダウンロードできるnpmからのpromise)は、このケースを正しく検出し、promiseのチェーンを単一のpromiseに集約します。これは、トップレベル関数によって返されるpromiseへの参照を保持しない場合に機能します(つまり、すぐに呼び出し、保持しないでください)。.then
良い:
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");
});
悪い:
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");
});
残念ながら、組み込みのpromise実装にはこの最適化がなく、実装する計画もありません。