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

时间:2021-11-30 02:31:05

I'm using Node.js streams to go through a text file line by line, make some transforms and output to a SVG file.

我正在使用Node.js流逐行浏览文本文件,进行一些转换并输出到SVG文件。

I am trying to write one last piece of data (</svg>) after the processing is done, however by the time the write stream emits the finish event, attempting to write() will throw Error: write after end.

我正在尝试在处理完成后写入最后一段数据( ),但是当写入流发出finish事件时,尝试write()将抛出Error:write after end。

Is there an elegant way I can solve this?

有一种优雅的方式可以解决这个问题吗?

Note: The input file is large (around 1GB) so there's no going around the pipe() method due to its I/O and memory management.

注意:输入文件很大(大约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);

Solutions

Thank you Jordan & pNre for helping me figuring this out.

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

Solution 1 (generic)

pipe() the write stream with the end:false option and manually end() the stream.

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 (specific to through/through2 transform streams)

through2 has a flush function that can be used to write the final data.

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

4 个解决方案

#1


8  

It would seem that pipe closes the stream when it is finished.

看来管道在完成时会关闭流。

The documentation at http://nodejs.org/api/stream.html states:

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.

默认情况下,当源流发送结束时,在目标上调用end(),以便该目标不再可写。传递{end:false}作为保持目标流打开的选项。

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

#2


2  

Have you considered creating a new stream to append the </svg> tag? through can help you with that:

您是否考虑过创建新流以附加 标记?通过可以帮助你:

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

#3


1  

It seems that there is an un-documented event called 'prefinish'. I have not used it though.

似乎有一个未记录的事件被称为'prefinish'。我没有用过它。

#4


0  

Recently ran in to this issue and found a more elegant solution. There's a (well) documented _flush method on the native Transform stream.

最近跑到这个问题,发现了一个更优雅的解决方案。在本机Transform流上有一个(井)记录的_flush方法。

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

The solution looks something like this:

解决方案看起来像这样:

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)

#1


8  

It would seem that pipe closes the stream when it is finished.

看来管道在完成时会关闭流。

The documentation at http://nodejs.org/api/stream.html states:

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.

默认情况下,当源流发送结束时,在目标上调用end(),以便该目标不再可写。传递{end:false}作为保持目标流打开的选项。

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

#2


2  

Have you considered creating a new stream to append the </svg> tag? through can help you with that:

您是否考虑过创建新流以附加 标记?通过可以帮助你:

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

#3


1  

It seems that there is an un-documented event called 'prefinish'. I have not used it though.

似乎有一个未记录的事件被称为'prefinish'。我没有用过它。

#4


0  

Recently ran in to this issue and found a more elegant solution. There's a (well) documented _flush method on the native Transform stream.

最近跑到这个问题,发现了一个更优雅的解决方案。在本机Transform流上有一个(井)记录的_flush方法。

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

The solution looks something like this:

解决方案看起来像这样:

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)