承诺中的某些人没有被执行

时间:2017-08-16 03:01:37

标签: promise bluebird

我试图读取并解析一个大的csv文件,对于每一行,我必须做一些异步计算,并在操作完成后增加计数器。所以我创建了一个Promise p并尝试将许多.then(xxx)链接起来,并在csv结束时读取它的最终.then(yyy)以输出计数。

但是这个数字并没有加起来。但是如果我p = p.then(xxx)p = p.then(yyy)这个数字会加起来(对于较小的csv文件)但我有时会遇到内存泄漏(对于大型csv文件)。

我做错了吗?

var fs = require('fs')
const csv = require('fast-csv');
var Promise = require('bluebird')
var count = 0;
var actual = 0;
let p = Promise.resolve();
const stream = fs.createReadStream(`/Users/ssmlee/Desktop/KingKong_Sims_5M.txt`);
const csvStream = csv({
    delimiter: ';'
})
.on('data', (row) => {
    count++
    if (count % 10000 === 0) {
        console.log(count)
        console.log(process.memoryUsage().heapUsed)
    }
  p.then(() => { // instead if we do p = p.then(() => it will work correctly
    return Promise.resolve().delay(5)
    .then(function() {
        actual++
    })
  });
})
.on('end', () => {
  p.then(() => { // instead if we do p = p.then(() => it will work correctly
    console.log(actual); // 4999977 or something like this
    console.log(count); // 5000000
  });
});
stream.pipe(csvStream);

2 个答案:

答案 0 :(得分:1)

如果您延迟增加actual计数,但从不等待承诺(抛出then的结果),则流可能会结束,并非所有增量都已发生。在您的示例中,23个回调仍在等待5ms延迟。顺便说一句,将所有这些链接在同一个p = Promise.resolve()上并没有多大意义,您可以立即执行所有操作。

如果你正在做p = p.then(…),那么你建立了一个很长的承诺链。这不应该泄漏任何内存,但会使用大量内存 - 所有5ms延迟按顺序链接在一起,并且您的脚本将运行(至少)25000秒。在开始时读入文件,生成数百万个promise,然后它们一个接一个地解析(并且可以被垃圾收集)。

要按顺序执行此顺序方法,您可能应该使用流的背压系统。

但你也可以同时等待延迟,一次没有太多活着的承诺:

p = Promise.all([p, Promise.delay(5)]).then(() => {
  actual++;
});

答案 1 :(得分:1)

嗯,你希望承诺并行运行,所以不能链接它们。

allp = [];
....
.on('data', (row) => {
    ...
    allp.push( p.then(() => {...}) );
}
...
.on('end', () => {
Promise.all(allp).then(() => {})

当然,您正在为每个活动创建一个Promise。

如果你需要在结束前解除承诺,那么你需要自己做。

既然您似乎对承诺的返回值不感兴趣,但只是在副作用(增加计数)中,您可以这样做

.on('data', (row) => {
    ...
    if (allp.length > 50) allp = [Promise.all(allp).then(()=>null)];
    allp.push( p.then(() => {...}) );
}

这样,50个承诺将被分组,一旦他们解决,他们将被一个承诺(将进入下一个50 ......)取代。

.then(()=>null)确保来自Promise.all的结果数组也被丢弃。 (相反,对于null的一个承诺将在allp中)

这取决于Promise.all的实现。如果Promise.all在解析时释放了每个promise(并且结果可用),那么这是完美的。

如果Promise.all等待所有50个承诺,然后全部释放它们,那么这仍然有效,除非每个50人组有一个非常长的运行承诺。

您可以使用延期承诺的反模式。

在开始时创建一个延期承诺。

var resolve;
var asyncRunningCount = 1; // start with 1
var p2 = new Promise(function() {
    resolve = arguments[0];
});

在开启数据

.on('data', (row) => {
    ...
    asyncRunningCount++;
    p.then(() => {work}) )
    .then(() { 
        asyncRunningCount--; 
        if (asyncRunningCount == 0) resolve(); // no more tasks running
    } );
}

.on('end', () => {
    asyncRunningCount--; 
    // remove the 1 that was set on start. No more new tasks will be added
    if (asyncRunningCount == 0) resolve(); // no more tasks running
    p2.then(() => { all done })

如果正在运行的任务计数暂时降至0,则启动时的值1会阻止p2被解析。

在开(结束)时,1递减。如果所有任务都完成,则asyncRunningCount将为0.这可以通过on(结束)的减量或on(数据)的减量来实现。

p2.then,将在所有任务完成后运行。

所有其他承诺在完成后将被释放。 实际上在(数据)中你不需要承诺。只需启动异步任务,当异步任务完成后,减少asyncRunningCount,并检查0。

这仍然意味着如果数据进入非常快,许多承诺会并行开始。 但是如果你没有启动承诺,那么你需要存储传入的数据,因此将以任何一种方式使用内存。