按顺序执行 Promise.all

4
我有一个包含许多 Promise 的数组,每个内部数组都可能包含 4,000、2,000 或 500 个 Promise。

总共大约有 60,000 个 Promise,我也可能使用其他值进行测试。

现在我需要执行 Promise.all(BigArray[0])

当第一个内部数组完成后,我需要依次执行下一个 Promise.all(BigArray[1]),以此类推。

如果我尝试执行 Promise.all(BigArray),则会抛出以下错误:

fatal error call_and_retry_2 allocation failed - process out of memory

我需要依次执行每个 Promise,而不是并行执行,这应该是 Node 所做的。 我不应该使用新的库,但愿意考虑这个答案!

编辑:

这里是一段示例代码:

function getInfoForEveryInnerArgument(InnerArray) {
    const CPTPromises = _.map(InnerArray, (argument) => getDBInfo(argument));
    return Promise.all(CPTPromises)
        .then((results) => {
            return doSomethingWithResults(results);
        });
}
function mainFunction() {
    BigArray = [[argument1, argument2, argument3, argument4], [argument5, argument6, argument7, argument8], ....];
    //the summ of all arguments is over 60k...
    const promiseArrayCombination = _.map(BigArray, (InnerArray, key) => getInfoForEveryInnerArgument(InnerArray));

    Promise.all(promiseArrayCombination).then((fullResults) => {
        console.log(fullResults);
        return fullResults;
    })
}

5
试图追踪60000个承诺的状态听起来像是一场噩梦,我不惊讶你的内存不足。看起来你需要进一步分解问题,或者重新思考架构。 - Heretic Monkey
2
我同意 @MikeMcCaughan 的观点。60k个Promise听起来不太合理,你的问题可能有更好的解决方案。 - nils
这个意思不是很清楚。如果你有一个巨大的数组,里面包含了许多 promise 数组,那么你的操作已经全部启动了。所以,这里没有串行执行的情况发生。它们已经在并行执行了。如果你只想知道所有的 promise 何时完成,那么请说出来,因为这听起来像是真正的问题。现在,在 node.js 中同时启动 60k 异步操作几乎没有意义,所以这可能是你真正的问题。我认为你需要回到代码中,展示一下创建 60k promises 的代码。这才是问题所在。 - jfriend00
2
他的问题很明确,他想要一种可以按顺序执行承诺的方法,而不是像Promise.all()那样并行执行。 - Evan Bechtol
正如我在问题中所解释的那样,每个innerArray都有500到4k个promises,Nodejs没有解决这么多promises的问题,我需要的是按顺序对每个innerArray进行promise.all。也就是说,在解决第一个innerArray后,先解决500-4k个promises,然后再移动到下一个innerArray,以此类推。 - Rodrigo Zurek
显示剩余3条评论
5个回答

6

Promise.all无法使用,您可以使用Array.reduce逐个处理BigArray元素:

BigArray.reduce((promiseChain, currentArray) => {
    return promiseChain.then(chainResults =>
        Promise.all(currentArray).then(currentResult =>
            [...chainResults, currentResult]
        )
    );
}, Promise.resolve([])).then(arrayOfArraysOfResults => {
    // Do something with all results
});

3
在ES2017中,使用async/await非常容易实现:
(async () => {
    for (let i = 0; i < BigArray.length; i++) {
        await Promise.all(BigArray(i));
    }
})();

1
你正在创建孤立的 Promise。你无法在父级 Promise 链中引用它们。 - Robert Mennell
@RobertMennell 谢谢您提出这个问题。您所提出的问题是未捕获的异常可能无法通过这种方法得到正确处理吗?我试图找出正确的处理方式;似乎 https://dev59.com/Tl0a5IYBdhLWcg3waYCM#30378082 可能是可行的解决方案。 - maksim

1
Promise.all()会并行检查传入的每个Promise结果,并在第一个错误时拒绝,或在所有Promise完成时解决。根据MDN所述,Promise.all()将从传递的可迭代对象中传递所有Promise的值数组。该值数组保留了原始可迭代对象的顺序,而不是Promise解析的顺序。如果在可迭代数组中传递的内容不是Promise,则通过Promise.resolve将其转换为Promise。如果传入的任何Promise拒绝,则所有Promise立即拒绝,并丢弃所有其他Promise,无论它们是否已解决。如果传入空数组,则此方法立即解决。
如果您需要按顺序执行所有承诺,那么Promise.all()方法将无法适用于您的应用程序。相反,您需要找到一种迭代解决承诺的方法。这会很困难;node.js本质上是异步的,并且使用循环(据我所知和经验),不会阻塞,直到从循环内的承诺接收到响应。

编辑:

存在一个名为promise-series-node的库,我认为它可能会对您有所帮助。由于您已经创建了承诺,因此只需将其传递给BigArray即可:

promiseSeries(BigArray).then( (results) => {
   console.log(results);
});

在我个人看来,您从60k+的承诺开始的方法不仅需要大量时间,而且会消耗执行它们的系统资源(这就是为什么您的内存不足)。我认为您可能需要考虑一个更好的应用程序架构。
编辑2,什么是Promise?:
Promise代表异步操作的结果,可以有三种状态之一:
1.待定:Promise的初始状态 2.已完成:表示成功操作的Promise状态 3.已拒绝:表示失败操作的Promise状态
Promise一旦进入已完成或已拒绝状态,就是不可变的。您可以链接Promise(非常适合避免重复的回调),也可以嵌套它们(当闭包是一个关注点时)。有许多伟大的文章在网上,这里是我发现的一个信息量很大的文章

2
你不能“执行承诺”。承诺代表已经开始的异步操作。你无法序列化已经开始的6万个异步操作。它们已经在并行运行了。因此,这样做对于帮助不清楚。 - jfriend00
1
根据您在此帖子中的所有评论,@jfriend00,似乎这里的一切都让您感到困惑。也许您可以发布一个答案并澄清一切。您说承诺不会执行,但在您对op的评论中,您说它们会执行。 - Evan Bechtol
1
我无法提供答案,因为问题本身就没有意义。真正的答案可能是不要一次性启动6万个异步操作,但是问题提出者没有展示任何相关代码,所以我们无法帮助解决实际问题。 - jfriend00
1
你似乎不理解,如果OP已经有了promise数组,那么这些异步操作已经在并行执行了。你不能执行promise。Promise是对已经启动的操作未来结果的表示。此时你能做的就是监控它们何时全部完成。可能没有任何情况需要同时运行60k个异步操作,因为node.js无法很好地处理这种情况,所以真正的解决方案可能是退后几步,停止一次性启动60k个异步操作。 - jfriend00
1
我非常清楚 Promise 的工作原理,你们正在争论措辞。如果你读了我的帖子,你会注意到我建议他改变他的方法。 - Evan Bechtol
显示剩余7条评论

0

这里有一个好的答案 在所有异步forEach回调完成后回调

function asyncFunction (item, cb) {
  setTimeout(() => {
    console.log('done with', item);
    cb(item*10);
  }, 1000*item);
}



let requests = [1,2,3].map((item) => {
    return new Promise((resolve) => {
      asyncFunction(item, resolve);
    });
})

Promise.all(requests).then(
//  () => console.log('done')
    function(arr){
        console.log(arr)
         console.log('done')
    }
    );

0

Promise库bluebird提供了一个辅助方法Promise.map,它将数组或数组的promise作为第一个参数,并将其所有元素映射到结果数组中,该结果数组也被promisified。也许你可以尝试这样做:

return Promise.map(BigArray, function(innerArray) {
  return Promise.all(innerArray);
})
.then(function(finishedArray) {
  // code after all 60k promises have been resolved comes here
  console.log("done");
});

但正如之前所述,这仍然是一项非常资源密集型的任务,可能会消耗所有可用的内存。


OP说他们有一个承诺数组的数组。我不清楚Promise.map()如何帮助解决这个问题。 - jfriend00
1
map() 函数将以并行方式解决 promise,但 Bluebird 的 mapSeries() 函数是 OP 问题的一个不错的解决方案。http://bluebirdjs.com/docs/api/promise.mapseries.html - Molomby
@Molomby 是的,你说得对。实际上这正是我的意图,只是我对map()的理解有误。我以为它会按顺序执行,但实际上是mapSeries()完成了任务。感谢你指出这一点。 - SaSchu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接