由于内存不足无法完成承诺

unable to complete promises due to out of memory

我有一个脚本可以抓取大约 1000 个网页。我正在使用 Promise.all 将它们一起触发,当所有页面都完成后 returns:

Promise.all(urls.map(url => scrap(url)))
    .then(results => console.log('all done!', results));

这很好而且正确,除了一件事 - 由于并发请求,机器 内存不足 。我使用 jsdom 进行报废,它很快就会占用几 GB 的内存,考虑到它实例化了数百个 window.

,这是可以理解的

我有一个修复的想法,但我不喜欢它。也就是说,将控制流更改为不使用 Promise.all,但链接我的承诺:

let results = {};
urls.reduce((prev, cur) =>
    prev
        .then(() => scrap(cur))
        .then(result => results[cur] = result)
        // ^ not so nice. 
, Promise.resolve())
    .then(() => console.log('all done!', results));

这不如 Promise.all... 性能不佳,因为它是链式的,必须存储返回值以供以后处理。

有什么建议吗?我应该改进控制流还是应该改进 scrap() 中的内存使用,或者有没有办法让节点限制内存分配?

您正在尝试 运行 1000 个并行的网络抓取。您将需要选择一些明显小于 1000 且 运行 一次仅选择 N 的数字,以便在这样做时消耗更少的内存。您仍然可以使用承诺来跟踪它们何时完成。

Bluebird's Promise.map() 可以通过将并发值作为选项传递来为您做到这一点。或者,您可以自己编写。

I have an idea to fix but I don't like it. That is, change control flow to not use Promise.all, but chain my promises:

你想要的是同时进行N个操作。排序是一种特殊情况,其中 N = 1 通常比并行执行其中一些(可能 N = 10)要慢得多。

This is not as good as Promise.all... Not performant as it's chained, and returned values have to be stored for later processing.

如果存储值是您内存问题的一部分,您可能不得不以任何方式将它们存储在内存之外的某个地方。您将不得不分析存储的结果使用了多少内存。

Any suggestions? Should I improve the control flow or should I improve mem usage in scrap(), or is there a way to let node throttle mem allocation?

使用Bluebird's Promise.map()或者自己写类似的东西。编写最多 运行 并行操作并保持所有结果有序的东西不是火箭科学,但要使其正确需要一些工作。我之前在另一个答案中介绍过它,但现在似乎无法找到它。我会继续寻找。

在这里找到我之前的相关答案: