Node.js 连接两个(或n个)流

Node.js 连接两个(或n个)流,node.js,stream,eventemitter,Node.js,Stream,Eventemitter,2条溪流: 给定可读的stream1和stream2,获取包含stream1和stream2连接的流的惯用(简洁)方法是什么 我不能做stream1.管道(外扩);stream2.pipe(outStream),因为这样流内容就混在一起了 n个流: 给定一个发射不确定数量流的 eventEmitter.emit('stream', stream1) eventEmitter.emit('stream', stream2) eventEmitter.emit('stream', stream3)

2条溪流:

给定可读的
stream1
stream2
获取包含
stream1
stream2
连接的流的惯用(简洁)方法是什么

我不能做
stream1.管道(外扩);stream2.pipe(outStream)
,因为这样流内容就混在一起了

  • n个流:

    给定一个发射不确定数量流的

    eventEmitter.emit('stream', stream1)
    eventEmitter.emit('stream', stream2)
    eventEmitter.emit('stream', stream3)
    ...
    eventEmitter.emit('end')
    
    用什么惯用的(简洁的)方法获得所有流连接在一起的流


  • 你也许可以让它更简洁,但这里有一个可行的方法:

    var util = require('util');
    var EventEmitter = require('events').EventEmitter;
    
    function ConcatStream(streamStream) {
      EventEmitter.call(this);
      var isStreaming = false,
        streamsEnded = false,
        that = this;
    
      var streams = [];
      streamStream.on('stream', function(stream){
        stream.pause();
        streams.push(stream);
        ensureState();
      });
    
      streamStream.on('end', function() {
        streamsEnded = true;
        ensureState();
      });
    
      var ensureState = function() {
        if(isStreaming) return;
        if(streams.length == 0) {
          if(streamsEnded)
            that.emit('end');
          return;
        }
        isStreaming = true;
        streams[0].on('data', onData);
        streams[0].on('end', onEnd);
        streams[0].resume();
      };
    
      var onData = function(data) {
        that.emit('data', data);
      };
    
      var onEnd = function() {
        isStreaming = false;
        streams[0].removeAllListeners('data');
        streams[0].removeAllListeners('end');
        streams.shift();
        ensureState();
      };
    }
    
    util.inherits(ConcatStream, EventEmitter);
    
    我们通过
    (流队列;
    到后面,从前面移动,
    正在流
    ,以及
    流发送
    )跟踪状态。当我们得到一个新的流时,我们推它,当一个流结束时,我们停止倾听并移动它。当流结束时,我们设置
    streamsend

    在每一个事件中,我们都会检查我们所处的状态。如果我们已经在流(管道流),我们什么也不做。如果队列为空并且设置了
    streamsend
    ,我们将发出
    end
    事件。如果队列中有内容,我们将恢复它并侦听其事件

    *请注意,
    pause
    resume
    是建议性的,因此某些流可能无法正常运行,需要缓冲。这个练习留给读者做

    在完成所有这些之后,我将通过构造一个
    EventEmitter
    ,用它创建一个
    ConcatStream
    ,然后发出两个
    stream
    事件,然后是一个
    end
    事件来执行
    n=2
    案例。我相信这可以做得更简洁一些,但我们不妨利用我们现有的资源。

    这个包将流连接起来。自述文件中的示例:

    var CombinedStream = require('combined-stream');
    var fs = require('fs');
    
    var combinedStream = CombinedStream.create();
    combinedStream.append(fs.createReadStream('file1.txt'));
    combinedStream.append(fs.createReadStream('file2.txt'));
    
    combinedStream.pipe(fs.createWriteStream('combined.txt'));
    
    我相信您必须一次附加所有流。如果队列为空,则组合流将自动结束。看

    该库是一个具有显式
    .end
    的替代方案,但它的受欢迎程度要低得多,而且可能没有经过很好的测试。它使用节点0.10的streams2 API(请参阅)。

    是一组基于节点1.0+流的流转换器和生成器,包括一种连接方法:

    var stream1ThenStream2 = streamee.concatenate([stream1, stream2]);
    
    是与Streams2兼容的组合流模块(如上所述)的替代品。它自动包装Streams1流

    组合流2的示例代码:

    var CombinedStream = require('combined-stream2');
    var fs = require('fs');
    
    var combinedStream = CombinedStream.create();
    combinedStream.append(fs.createReadStream('file1.txt'));
    combinedStream.append(fs.createReadStream('file2.txt'));
    
    combinedStream.pipe(fs.createWriteStream('combined.txt'));
    

    这可以用香草nodej来完成

    import { PassThrough } from 'stream'
    const merge = (...streams) => {
        let pass = new PassThrough()
        let waiting = streams.length
        for (let stream of streams) {
            pass = stream.pipe(pass, {end: false})
            stream.once('end', () => --waiting === 0 && pass.emit('end'))
        }
        return pass
    }
    

    如果您不关心流中数据的顺序,那么在中只需一个简单的操作就可以了


    干杯;)

    使用ECMA 15+并结合IvoFeng的良好答案,对香草NODEJ进行

    该类是一个普通流,它不会以任何方式修改流

    const { PassThrough } = require('stream');
    
    const concatStreams = (streamArray, streamCounter = streamArray.length) => streamArray
      .reduce((mergedStream, stream) => {
        // pipe each stream of the array into the merged stream
        // prevent the automated 'end' event from firing
        mergedStream = stream.pipe(mergedStream, { end: false });
        // rewrite the 'end' event handler
        // Every time one of the stream ends, the counter is decremented.
        // Once the counter reaches 0, the mergedstream can emit its 'end' event.
        stream.once('end', () => --streamCounter === 0 && mergedStream.emit('end'));
        return mergedStream;
      }, new PassThrough());
    
    可以这样使用:

    const mergedStreams = concatStreams([stream1, stream2, stream3]);
    

    下面的代码适用于我:)。从前面给出的所有答案中获取输入

      const pipeStreams = (streams) => {
      const out = new PassThrough()
      // Piping the first stream to the out stream
      // Also prevent the automated 'end' event of out stream from firing
      streams[0].pipe(out, { end: false })
      for (let i = 0; i < streams.length - 2; i++) {
        // On the end of each stream (until the second last) pipe the next stream to the out stream
        // Prevent the automated 'end' event of out stream from firing
        streams[i].on('end', () => {
          streams[i + 1].pipe(out, { end: false })
        })
      }
      // On the end of second last stream pipe the last stream to the out stream.
      // Don't prevent the 'end flag from firing'
      streams[streams.length - 2].on('end', () => {
        streams[streams.length - 1].pipe(out)
      })
      return out
    } 
    
    constpipestreams=(streams)=>{
    const out=new PassThrough()
    //将第一条流输送到输出流
    //还可以防止触发out stream的自动“结束”事件
    流[0]。管道(输出,{end:false})
    for(设i=0;i{
    流[i+1]。管道(输出,{end:false})
    })
    }
    //在第二个最后一个流的末端,将最后一个流输送到输出流。
    //不要阻止“结束标志发射”
    streams[streams.length-2]。在('end',()=>{
    streams[streams.length-1]。管道(输出)
    })
    返回
    } 
    
    这里两个投票最多的答案都不适用于异步流,因为不管源流是否已准备好生成,它们只是通过管道传输。我必须将内存中的字符串流与来自数据库的数据源相结合,并且数据库内容始终位于结果流的末尾,因为获得db响应需要一秒钟的时间。以下是我为自己的目的而写的东西

    export function joinedStream(...streams: Readable[]): Readable {
      function pipeNext(): void {
        const nextStream = streams.shift();
        if (nextStream) {
          nextStream.pipe(out, { end: false });
          nextStream.on('end', function() {
            pipeNext();
          });
        } else {
          out.end();
        }
      }
      const out = new PassThrough();
      pipeNext();
      return out;
    }
    

    现在可以使用异步迭代器轻松地完成此操作

    异步函数*concatStreams(可读){
    for(可读内容的常量){
    对于wait(const chunk of readable){yield chunk}
    }
    } 
    
    你可以这样使用它

    const fs=require('fs'))
    const stream=require('stream')
    const files=['file1.txt','file2.txt','file3.txt']
    const iterable=wait concatStreams(files.map(f=>fs.createReadStream(f)))
    //将异步iterable转换为可读流
    const mergedStream=stream.Readable.from(iterable)
    

    有关异步迭代器的更多信息:

    谢谢Aaron!我有点希望会有一些现有的库,这样我可以用三行代码来解决它。如果没有,我想我可能会将您的解决方案提取到一个包中。我可以在麻省理工许可证下使用你的代码吗?啊,找到了流库。看看我的答案。@JoLiss我也先找了些东西,但没找到那个选项。如果你还想的话,你当然可以在库中使用我的代码。谢谢,我会查出来的。我想这就是节点0.10?是的节点0.10,但您可以将旧式流包装为0.10+流,如READMEShouldn中所述。您不是从reduce返回了一些内容吗?这看起来像是未定义的
    joined
    。警告:这将导致所有流并行地通过管道传输到直通流,而不考虑数据的顺序,很可能会损坏您的数据。@LeonLi这确实是此方法的目的。如果要保留顺序,可以将不同于PassThrough的初始值传递给reduce函数;)@Ivo这个问题问的是连接。因此,本QA中的大多数读者都关心订购。
    export function joinedStream(...streams: Readable[]): Readable {
      function pipeNext(): void {
        const nextStream = streams.shift();
        if (nextStream) {
          nextStream.pipe(out, { end: false });
          nextStream.on('end', function() {
            pipeNext();
          });
        } else {
          out.end();
        }
      }
      const out = new PassThrough();
      pipeNext();
      return out;
    }