将大量(>40,000)原生承诺链接在一起会消耗太多内存

Chaining a lot of (>40,000) native promises together eats up too much memory

本文关键字:在一起 链接 承诺 原生 内存 太多      更新时间:2023-09-26

我有一个项目,我需要编写一个函数来依次计算几件事,然后将结果写入SQL DB。不幸的是,我要重复4万多次。我使用node.js,并承诺可以实现这一点,但内存使用量几乎达到2GB,很多时候程序在进行10,000次左右的计算后就会停止运行。我开发了自己的原生promiseEach函数,它按顺序接受数组项,并将其与promise链接在一起。

我哪里做错了?:

function promiseEach(array,promiseFn){
    return new Promise(function(resolve,reject){
        try {
            var promiseArray = [];
            var json = { array: array, counter: 0 }
            for (var i = 0; i < array.length; i++) { 
                promiseArray.push(promiseFn) 
            }
            promiseArray.reduce(function(preFn,curFn,index,pArray){
                return  preFn
                .then( function(z){ return json.array[json.counter++] })
                .then(curFn)
            }, 
            Promise.resolve(json.array[json.counter]))
            .then(resolve,reject)
        }catch(err){
            console.log("promiseEach ERROR:");
            reject(err)
        }
    })
}

一开始你就把函数弄得太复杂了——据我所知,你对数组的每个内容都按顺序调用了promiseFn

下面的代码在功能上与你的代码

相同
function promiseEach(array, promiseFn) {
    return array.reduce(function(prev, item) {
        return prev.then(function() {
            return promiseFn(item);
        });
    }, Promise.resolve());
}

不能保证这将解决实际问题,尽管

的完整性,因为你是在nodejs编码,相同的代码写在ES2015+

let promiseEach = (array, promiseFn) => 
    array.reduce((prev, item) => 
        prev.then(() => 
            promiseFn(item)
        )
    , Promise.resolve());

发布代码中的两行

    .then( function(z){ return json.array[json.counter++] })
    then(curFn)

似乎表明promseFn将在前一个调用对其执行的操作完成后带一个新参数被调用,并且前一个调用的完成值不会在承诺链中使用。

为了避免在从每个函数返回之前创建(39,999左右)中间链式承诺,建议使用promiseFn返回的中间承诺在它们被实现时调用承诺工厂函数,并返回一个仅由最后一个中间承诺实现的最终承诺。

下面的概念代码不包含对reduce的调用,因为没有执行reduce操作:

function promiseEach( array, promiseFn) {
    var resolve, reject;
    var counter = 0;
    var final = new Promise( function( r, j) { resolve = r; reject = j});
    function nextPromise() {
        promiseFn( array[counter++])
        .then( (counter < array.length ? nextPromise : resolve), reject);
    }
    if( array.length) {
        nextPromise();
    }
    else {
        reject( new Error("promiseEach called on empty array"));
    }
    return final;
}

添加注释:

上面的promiseEach函数在node/js中使用同步和异步promiseFn进行测试,不并发更新数据数组

    同步测试
  • function promiseSynch( z) {
        return new Promise( function(resolve,reject){resolve(z);});
    }
    

能够在大约10秒内处理一个包含100万个(1000000个)条目的数组,使用一台内存为1GB的Win7 i86笔记本,同时打开浏览器、编辑器和任务管理器。内存使用率稳定在可用内存的80%左右。

    异步测试
  • function promiseAsynch( z) {
        var resolve;
        function delayResolve() {
            resolve( z);
        }
        return new Promise( ( r, j)=>{
            resolve = r;
            setTimeout(delayResolve,1);
        });
    }
    

在20分钟多一点的时间内,在同一个笔记本上管理了100,000个条目的数组,内存使用率也在80%左右。

结论

测试表明承诺不会仅仅因为它们正在被使用而导致内存泄漏,并且它们应该能够处理将冗长的数组数据依次传递到承诺返回函数。

这意味着在完全解决问题之前,需要找到内存分配失败或神秘的处理中断的其他原因。作为一个建议,您可以从搜索与正在使用的DB库相关的内存泄漏问题开始。