Node.js将相同的可读流管道化为多个(可写)目标

时间:2013-10-23 22:56:33

标签: javascript node.js stream pipe node.js-stream

我需要运行两个需要从同一个流中读取数据的命令。 在将流传输到另一个流后,缓冲区被清空,因此我无法再次从该流中读取数据,因此这不起作用:

var spawn = require('child_process').spawn;
var fs = require('fs');
var request = require('request');

var inputStream = request('http://placehold.it/640x360');
var identify = spawn('identify',['-']);

inputStream.pipe(identify.stdin);

var chunks = [];
identify.stdout.on('data',function(chunk) {
  chunks.push(chunk);
});

identify.stdout.on('end',function() {
  var size = getSize(Buffer.concat(chunks)); //width
  var convert = spawn('convert',['-','-scale',size * 0.5,'png:-']);
  inputStream.pipe(convert.stdin);
  convert.stdout.pipe(fs.createWriteStream('half.png'));
});

function getSize(buffer){
  return parseInt(buffer.toString().split(' ')[2].split('x')[0]);
}

请求抱怨此

Error: You cannot pipe after data has been emitted from the response.

并将 inputStream 更改为fs.createWriteStream会产生同样的问题。 我不想写入文件,但重用以某种方式请求生成的流(或其他任何内容)。

有没有办法在完成管道后重复使用可读流? 什么是完成上述例子的最佳方式?

6 个答案:

答案 0 :(得分:72)

您必须通过将流传输到两个流来创建流的副本。您可以使用PassThrough流创建一个简单的流,它只是将输入传递给输出。

const spawn = require('child_process').spawn;
const PassThrough = require('stream').PassThrough;

const a = spawn('echo', ['hi user']);
const b = new PassThrough();
const c = new PassThrough();

a.stdout.pipe(b);
a.stdout.pipe(c);

let count = 0;
b.on('data', function (chunk) {
  count += chunk.length;
});
b.on('end', function () {
  console.log(count);
  c.pipe(process.stdout);
});

输出:

8
hi user

答案 1 :(得分:2)

对于一般问题,以下代码可以正常工作

var PassThrough = require('stream').PassThrough
a=PassThrough()
b1=PassThrough()
b2=PassThrough()
a.pipe(b1)
a.pipe(b2)
b1.on('data', function(data) {
  console.log('b1:', data.toString())
})
b2.on('data', function(data) {
  console.log('b2:', data.toString())
})
a.write('text')

答案 2 :(得分:2)

如果不同时输入两个或多个流?

例如:

var PassThrough = require('stream').PassThrough;
var mybiraryStream = stream.start(); //never ending audio stream
var file1 = fs.createWriteStream('file1.wav',{encoding:'binary'})
var file2 = fs.createWriteStream('file2.wav',{encoding:'binary'})
var mypass = PassThrough
mybinaryStream.pipe(mypass)
mypass.pipe(file1)
setTimeout(function(){
   mypass.pipe(file2);
},2000)

上面的代码不会产生任何错误,但file2为空

答案 3 :(得分:1)

您可以使用我创建的这个小npm软件包:

readable-stream-clone

借助此功能,您可以根据需要多次重复使用可读流

答案 4 :(得分:0)

我有一个不同的解决方案同时写入两个流,自然,写入的时间将是两次的添加,但我用它来响应下载请求,我想保留一份副本在我的服务器上下载了文件(实际上我使用了S3备份,因此我在本地缓存了最常用的文件以避免多个文件传输)

/**
 * A utility class made to write to a file while answering a file download request
 */
class TwoOutputStreams {
  constructor(streamOne, streamTwo) {
    this.streamOne = streamOne
    this.streamTwo = streamTwo
  }

  setHeader(header, value) {
    if (this.streamOne.setHeader)
      this.streamOne.setHeader(header, value)
    if (this.streamTwo.setHeader)
      this.streamTwo.setHeader(header, value)
  }

  write(chunk) {
    this.streamOne.write(chunk)
    this.streamTwo.write(chunk)
  }

  end() {
    this.streamOne.end()
    this.streamTwo.end()
  }
}

然后,您可以将其用作常规OutputStream

const twoStreamsOut = new TwoOutputStreams(fileOut, responseStream)

并将其传递给您的方法,就好像它是一个响应或一个fileOutputStream

答案 5 :(得分:0)

如果您对PassThrough流进行异步操作,则此处发布的答案将无效。 适用于异步操作的解决方案包括缓冲流内容,然后根据缓冲结果创建流。

  1. 要缓冲结果,可以使用concat-stream

    const Promise = require('bluebird');
    const concat = require('concat-stream');
    const getBuffer = function(stream){
        return new Promise(function(resolve, reject){
            var gotBuffer = function(buffer){
                resolve(buffer);
            }
            var concatStream = concat(gotBuffer);
            stream.on('error', reject);
            stream.pipe(concatStream);
        });
    }
    
  2. 要从缓冲区创建流,可以使用:

    const { Readable } = require('stream');
    const getBufferStream = function(buffer){
        const stream = new Readable();
        stream.push(buffer);
        stream.push(null);
        return Promise.resolve(stream);
    }