使用递归承诺停止内存泄漏

eng*_*gie 13 javascript recursion memory-leaks promise q

如何Promise使用Q库创建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>
Run Code Online (Sandbox Code Playgroud)

For*_*say 12

这不会堆栈溢出,因为promises会破坏堆栈,但它会泄漏内存.如果在node.js中运行相同的代码,则会出现如下错误:

FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
Run Code Online (Sandbox Code Playgroud)

这里发生的事情是,正在创建一个非常长的嵌套承诺链,每个承诺都在等待下一个.你需要做的是找到一种方法来压扁该链,以便只返回一个顶级承诺,等待目前代表一些实际工作的最内在的承诺.

打破链条

最简单的解决方案是在顶层构建一个新的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");
});
Run Code Online (Sandbox Code Playgroud)

虽然这个解决方案有点不优雅,但您可以确定它可以在所有承诺实现中使用.

那么/ promise现在支持尾递归

一些承诺实现(例如来自npm的承诺,您可以从https://www.promisejs.org/下载为独立库)正确检测此案例并将承诺链合并为单个承诺.这可以让你不要保留对顶级函数返回的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");
});
Run Code Online (Sandbox Code Playgroud)

坏:

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");
});
Run Code Online (Sandbox Code Playgroud)

不幸的是,没有任何内置的promise实现具有这种优化,并且没有任何计划实现它.


pet*_*est 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>
Run Code Online (Sandbox Code Playgroud)