Node.js:如何在 Write Stream 'finish' 事件上 write()

Node.js: How to write() on Write Stream 'finish' event

我正在使用 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);

解决方案

谢谢 Jordan & pNre 帮我解决这个问题。

解决方案 1(通用)

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>');
});

解决方案 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);

似乎管道在完成时关闭了流。

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

By default end() is called on the destination when the source stream emits end, so that destination is no longer writable. Pass { end: false } as options to keep the destination stream open.

This keeps writer open so that "Goodbye" can be written at the end.

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

似乎有一个名为 'prefinish' 的未记录事件。 不过我没用过。

您是否考虑过创建一个新流来附加 </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);

最近 运行 解决了这个问题,找到了一个更优雅的解决方案。本机 T运行sform 流上有一个(很好)记录的 _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)