从readline的线路回调中进行异步调用?

时间:2018-10-09 16:19:29

标签: node.js async-await

我目前有此工作代码(此处略有简化);这是将readline包装在promise中的标准习惯用法。

function process(fname, options){
  return new Promise(function (resolve, reject) {
    const stats = {cnt:0,error:0,mse:0.0}
    const reader = require('readline').createInterface({
        input: fs.createReadStream(fname),
        })
    reader.on('error', reject) // Reject the promise, when an error
    reader.on('line', function (row) {
        stats.error += doSync(row)
        stats.cnt++
        })
    reader.on('close', function () {
      if (stats.cnt >= 1)stats.mse = stats.error / stats.cnt
      resolve(stats)
    })
  })
}

我想将呼叫doSync()更改为doAsync()(这将返回一个承诺)。

我尝试更改此设置:

reader.on('line', function (row) {
    stats.error += doSync(row)
    stats.cnt++
    })

收件人:

reader.on('line', async function (row) {
    stats.error += await doAsync(row)
    stats.cnt++
    })

但是没有用。具体来说,它将处理doAsync()直到异步的实际事物(对shell命令的调用),然后立即继续进行下一行。对于文件中的所有行。然后脚本坐在那里,我们互相瞪着眼睛。

我的直觉是readline忽略了返回的承诺,我无能为力。但我希望蜂巢的想法有一些想法。

我在节点8.12.0上,但是升级到10.x并非不可能。 而且我不限于使用readline。 (但我只能逐行处理输入文件!)

更新:

注意:我的doAsync()原来有错误。但是,即使解决了问题,readline仍然无法正常工作。

从readline切换到逐行修复了该问题。 (这几乎是一个直接的替代;但是将“关闭”更改为“结束”事件。)可接受的答案是更多的代码,但同样有效。

在比较测试中,Transform方法花费了1m 48s至1m49s,逐行方法花费了1m 49s至1m 51s。 (注意:每次只运行两次,但这足以说服我他们基本上是相同的。)

使用reader.pause() / resume()对阅读线没有帮助,逐行也不需要(使用它确实可以一次执行严格的处理,但是如果没有,它仍然可以正常工作)。

1 个答案:

答案 0 :(得分:1)

使用Transform流可能是异步处理每一行的最佳方法。

const {Transform} = require('stream');

您可以通过简单的转换替换readline库:

function toLines() {
    let line = '';
    return new Transform({
        decodeStrings: false,
        readableObjectMode: true,
        transform(chunk, encoding, callback) {
            const lines = chunk.split('\n');

            line += lines.shift();
            while (lines.length) {
                this.push(line);
                line = lines.shift();
            }

            callback();
        },
        flush(callback) {
            if (line) {
                this.push(line);
            }
            callback();
        }
    });
}

并实施另一个收集“统计信息”的Transform

function toStats() {
    const stats = {cnt: 0, error: 0, mse: 0.0};
    return new Transform({
        objectMode: true,
        async transform(line, encoding, callback) {
            stats.error += await doAsync(line);
            stats.cnt++;
            callback();
        },
        flush(callback) {
            if (stats.cnt >= 1)stats.mse = stats.error / stats.cnt;
            callback(null, stats);
        }
    });
}

然后,您可以实现process来利用转换:

async function process(fname, options) {
    return new Promise((resolve, reject) => {
        fs.createReadStream(fname, {encoding: 'utf8'})
            .pipe(toLines())
            .pipe(toStats())
            .on('error', reject)
            .on('data', resolve);
    });
}