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

Mar*_*hii 65 javascript pipe stream node.js 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]);
}
Run Code Online (Sandbox Code Playgroud)

请求抱怨这个

Error: You cannot pipe after data has been emitted from the response.
Run Code Online (Sandbox Code Playgroud)

并改变inputStreamfs.createWriteStream当然会产生同样的问题.我不想写入文件,而是以某种方式重用请求生成的流(或其他任何内容).

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

use*_*109 75

您必须通过将流管道传输到两个流来创建流的副本.您可以使用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);
});
Run Code Online (Sandbox Code Playgroud)

输出:

8
hi user
Run Code Online (Sandbox Code Playgroud)

  • 我很困惑,你说你不能处理相同的流两次,但你的解决方案是......处理相同的流两次(使用PassThrough转换).这似乎是矛盾的.这是关于stdout流的特别之处吗? (40认同)
  • 请注意,此技术仅在生成的命令输出不填充背压缓冲区的字节数时才有效.你可以尝试使用a = spawn('head',[' - c','200K','/ dev/urandom']);使其失败.如果没有输出c,在某些时候,a.stdout会暂停输出.b会流失,永不结束. (14认同)
  • 我测试了这个,它确实有效.我认为你说"你不能两次处理相同的流"是不正确的,因为那就是你正在做的事情.你的第一个关于在"结束"之后无法管道流的陈述是正当理由. (6认同)
  • 使用此技术与Haraka邮件服务器附件挂钩将传入流传输到多个邮件帐户数据库.这个答案有效. (4认同)
  • 不要使用此方法,因为如果以不同的速率读取流,则会产生问题.试试这个而不是https://www.npmjs.com/package/readable-stream-clone对我来说效果很好. (4认同)
  • 用图像测试了这个答案,就像@Jerome WAGNER所说的,如果文件足够小,它就可以工作。它对我不起作用,因为请求从未结束,并且出现“套接字挂断”错误。 (2认同)
  • 我认为这不再是一个有效的声明:https://github.com/nodejs/readable-stream/blob/master/lib/_stream_readable.js#L508可读流可以将数据传输到多个可写流. (2认同)
  • @ inf3rno您的意思是https://github.com/nodejs/ready-stream/blob/7bda7cc5c63fbd5cd63a3b08b424b3890771a7fb/lib/_stream_visible.js#L508。:-) (2认同)

小智 12

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

以下模式对我来说非常有效.它使用基于Stream2流,Streamz和Promises的库通过回调同步异步流.使用第一个答案中熟悉的示例:

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); });
Run Code Online (Sandbox Code Playgroud)


lev*_*per 6

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

readable-stream-clone

这样,您可以根据需要多次重用可读流

  • 它是否受到上面描述的背压问题的影响(/sf/ask/1368768621/#comment38541924_19561718)?从第二个管道生成一个[空文件](/sf/ask/1368768621/#comment86192248_40874999)怎么样?如果你能详细说明一点那就太棒了(对我和你的包声誉:-))。提前致谢! (3认同)

Jak*_*ake 5

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

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')
Run Code Online (Sandbox Code Playgroud)