使用递归承诺来阻止内存泄漏

Stop memory leaks with recursive promises

本文关键字:内存 泄漏 递归 承诺      更新时间:2023-09-26

如何使用Q库创建JavaScript Promise的递归链?以下代码无法在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>

这不会导致堆栈溢出,因为promise会破坏堆栈,但会泄漏内存。如果你在node.js中运行同样的代码,你会得到一个错误,上面写着:

FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory

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

断链

最简单的解决方案是在顶层构建一个新的承诺,并使用它来打破递归:

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实现(例如来自npm的promise,您可以从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");
});

错误:

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实现有这种优化,也没有任何实现它的计划。

下面是您尝试做的事情的最简单的实现,如果它有效,那么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>