Node.js:如何在可写流的“finish”事件上执行write()操作

7
我正在使用Node.js流逐行读取文本文件,进行一些转换并输出到SVG文件。但是在处理完成后,我尝试写入最后一个数据(</svg>),然而在写入流发出finish事件时,尝试调用write()将抛出Error: write after end的错误。有没有一种优雅的方法可以解决这个问题?请注意:输入文件很大(约1GB),因此由于其I / O和内存管理,无法绕过pipe()方法。
var fs = require('fs');
var split2 = require('split2');
var through2 = require('through2');

var read_stream = fs.createReadStream('input.txt');
var write_stream = fs.createWriteStream('output.svg');

write_stream.write('<svg>');
write_stream.on('finish', function() {
  this.write('</svg>'); // doesn't work
});

read_stream
  .pipe(split2())
  .pipe(through2.obj(function(line, encoding, next) {
     this.push(line);
     next();
  }))
  .pipe(write_stream);

解决方案

感谢JordanpNre帮助我找到了解决方法。

通用解决方案

使用pipe()将写入流与end:false选项连接起来,并手动调用end()方法关闭流。

var fs = require('fs');
var split2 = require('split2');
var through2 = require('through2');

var read_stream = fs.createReadStream('input.txt');
var write_stream = fs.createWriteStream('output.svg');

write_stream.write('<svg>');

read_stream
  .pipe(split2())
  .pipe(through2.obj(function(line, encoding, next) {
     this.push(line);
     next();
  }))
  .pipe(write_stream, { end: false });

read_stream.on('end', function() {
  write_stream.end('</svg>');
});

Solution 2(针对through/through2转换流的特定解决方案)

through2有一个flush函数,可用于写入最终数据。

var fs = require('fs');
var split2 = require('split2');
var through2 = require('through2');

var read_stream = fs.createReadStream('input.txt');
var write_stream = fs.createWriteStream('output.svg');

write_stream.write('<svg>');

read_stream
  .pipe(split2())
  .pipe(through2.obj(function(line, encoding, next) {
    this.push(line);
    next();
  }, function(flush) {
    this.push('</svg>');
    flush();
  }))
  .pipe(write_stream);

1
你好!我建议将你的解决方案从问题部分提取到答案部分。 - Slava Fomin II
@SlavaFominII,你能给我一些关于如何继续的指导吗? - Dan
1
@DanBurzo,我认为SlavaFominII建议您编辑您的问题并删除您附加的解决方案。然后,您可以创建自己的答案来回答您的问题,或通过评论向Jordan和/或pNre的答案提出建议修改。 - haysclark
4个回答

10

看起来当pipe完成时,它会关闭流。

http://nodejs.org/api/stream.html文档指出:

默认情况下,在源流发出end事件时会调用end()方法来关闭目标流,因此目标流不再可写。传递{ end: false }选项可以保持目标流处于打开状态。

这将使writer保持打开状态,以便最后可以写入“Goodbye”。

reader.pipe(writer, { end: false });
reader.on('end', function() {
  writer.end('Goodbye\n');
});

1
感谢您抽出时间查看问题。我已添加了澄清说明:文件非常大,因此我不能放弃使用pipe(),否则会耗尽内存。 - Dan
@Dan,我在网站上找到了你所需要的内容,我会更新我的答案。 - Jordan Honeycutt
啊,我在文档中完全错过了这个!谢谢,这正是我所需要的。 - Dan

3

您是否考虑创建一个新的流来追加</svg>标记?through可以帮助您实现这一点:

var fs = require('fs');
var split2 = require('split2');
var through = require('through');

var read_stream = fs.createReadStream('input.txt');
var write_stream = fs.createWriteStream('output.svg');

write_stream.write('<svg>');
var tag = through(function write(data) {
    this.queue(data);
}, function end() {
    this.queue('</svg>');
});

read_stream.pipe(split2()).pipe(some_transform).pipe(tag).pipe(write_stream);

啊,当然!我正在使用through2进行转换,所以我可以在其中添加一个flush函数。我知道这是可用的,但在寻找解决方案时它没有在我的意识中注册 :-) 谢谢! - Dan

1

看起来有一个名为'prefinish'的未记录事件。

尽管我没有使用过它。


0
最近遇到了这个问题,并找到了更优雅的解决方案。原生的Transform流上有一个(很好)文档化的_flush方法。

https://nodejs.org/api/stream.html#stream_transform_flush_callback

解决方案看起来像这样:

const fs = require('fs')
const split2 = require('split2')
const { Transform } = require('stream')

const input = fs.createReadStream('input.txt')
const output = fs.createWriteStream('output.svg')

class SVGWrapper extends Transform {
    constructor(){ this.push('<svg>') }

    _flush(done){ this.push('</svg>') }

    _transform(line, enc, next){
        this.push(line)
        next()
    }
}

input
    .pipe(split2())
    .pipe(new SVGWrapper)
    .pipe(output)

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