13

Q ライブラリPromiseを使用して JavaScript の再帰チェーンを作成するにはどうすればよいですか? 次のコードは、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>
4

2 に答える 2

13

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実装にはこの最適化がなく、実装する計画もありません。

于 2013-02-23T01:41:42.410 に答える
2

以下は、あなたがやろうとしていることの最も単純な実装です。これが機能する場合は、q ライブラリに問題があります。そうでない場合は、javascript に深刻な問題があります。

<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>
于 2013-02-22T17:08:02.607 に答える