Javascript Node.js将相同的可读流输送到多个(可写)目标中

Javascript Node.js将相同的可读流输送到多个(可写)目标中,javascript,node.js,stream,pipe,node.js-stream,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(

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

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
当然也会产生同样的问题。 我不想写入文件,而是以某种方式重用请求产生的流(或任何其他流)

有没有一种方法可以在可读流完成管道后重用它?
完成上述示例的最佳方法是什么?

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

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

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

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

只有当流处理数据所用的时间大致相同时,第一个答案才有效。如果需要的时间明显更长,则请求新数据的速度越快,因此会覆盖速度较慢的用户仍在使用的数据(在尝试使用重复流解决此问题后,我遇到了此问题)

下面的模式对我很有效。它使用基于Stream2 streams、Streamz的库,并承诺通过回调同步异步流。使用第一个答案中熟悉的示例:

spawn = require('child_process').spawn;
pass = require('stream').PassThrough;
streamz = require('streamz').PassThrough;
var Promise = require('bluebird');

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

a.stdout.pipe(streamz(combineStreamOperations)); 

function combineStreamOperations(data, next){
  Promise.join(b, c, function(b, c){ //perform n operations on the same data
  next(); //request more
}

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

不同时将管道输送到两个或多个流中如何

例如:

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)

上面的代码不会产生任何错误,但文件2是空的

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

然后可以将其用作常规输出流

const twoStreamsOut = new TwoOutputStreams(fileOut, responseStream)

并将其作为响应或fileOutputStream传递给您的方法

如果您对传递流进行异步操作,则此处发布的答案将不起作用。
const twoStreamsOut = new TwoOutputStreams(fileOut, responseStream)
适用于异步操作的解决方案包括缓冲流内容,然后根据缓冲结果创建流

  • 要缓冲结果,可以使用

  • 要从缓冲区创建流,可以使用:

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

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

    可读流克隆


    有了它,您可以根据需要多次重用可读流

    似乎您正在使用imagemick。您可以将值传递到-scale以进行缩放。您也可以使用@user568109。是的,这不是这里的问题。这是一个更一般的问题……这是imagemagick,因为它可以是任何其他命令/流将此技术用于Har又名mailserver附件挂钩,用于将传入流导入多个邮件帐户数据库。此答案有效。请注意,此技术仅在生成的命令输出未填充背压缓冲区的字节数时有效。您可以尝试使用a=spawn('head',['-c','200K','/dev/uradom'])使其失败;如果c没有被管道输出,在某一点上,a.stdout将暂停管道输出。b将耗尽并永不结束。我很困惑,你说你不能处理同一个流两次,但你的解决方案是..处理同一个流两次(使用PassThrough转换)。这似乎是矛盾的。这是stdout流的特殊之处吗?我测试了它,它肯定有效。我认为你说“你不能处理同一个流两次”是不正确的,因为这正是您正在做的。您第一次声明在流“结束”后无法对其进行管道传输是一个恰当的理由。不要使用此方法,因为如果以不同的速率读取流,则会产生问题。尝试此方法对我很有效。在某种程度上,它对我有帮助!我认为您已经发现了一个问题,但它令人困惑因为这不是一个答案。哪个部分实际上覆盖了数据?被覆盖的代码自然会抛出一个错误。它是否受到所描述的背压问题的影响?从第二个管道生成一个管道怎么样?如果你能详细说明一点,那将非常棒(对我和你的包声誉:-))。提前谢谢!
    const { Readable } = require('stream');
    const getBufferStream = function(buffer){
        const stream = new Readable();
        stream.push(buffer);
        stream.push(null);
        return Promise.resolve(stream);
    }