【问题标题】:NodeJS streams and premature endNodeJS 流和过早结束
【发布时间】:2015-10-07 05:13:28
【问题描述】:

假设 NodeJS 中的可读流和与之关联的 Data (on('data', ...)) 事件处理程序相对较慢,End 事件是否可能在最后一个 Data 处理程序完成之前触发,如果是这样,它会过早终止该处理程序吗?或者,是否所有 Data 事件都会被调度并运行?

在我的情况下,我正在处理大文件并且希望将每个数据块提交到数据库。如果在处理程序中的最后一个 DB 调用实际完成之前触发 End,我担心我可能会丢失最后一个或两个(或更多)记录。

【问题讨论】:

    标签: node.js events stream event-handling


    【解决方案1】:

    在最后一个“数据”事件之后触发事件“结束”。但它可能发生在最后一个数据处理程序完成之前。有可能在一个“数据”处理程序完成之前,下一个开始。这取决于您的代码中的内容,但稍后对事件“数据”的调用可能会在更早之前完成。它可能会导致您的代码出现错误和问题。

    如何导致问题的示例(针对您自己的测试):

      var fs = require('fs');
      var rr = fs.createReadStream('somebigfile.jpg');
      var i=0;
      rr.on('data', function(chunk) {
        i++;
        var s = i;
        console.log('readable:' + s);
        setTimeout(function(){
          console.log('timeout:'+s);
        }, 50-i*10);
      });
      rr.on('end', function() {
        console.log('end');
      });
    

    当启动每个“数据”事件处理程序时,它将在您的控制台中打印。几毫秒后完成。完成可能有不同的顺序。

    解决方案:

    可读流有两种模式“流动模式”和“暂停模式”。当您添加“数据”事件处理程序时,您会自动将可读流设置为流动模式。

    来自documentation

    在流动模式下,从底层系统读取数据并 尽快提供给您的程序

    在这种模式下,事件不会等待您的慢动作完成。您需要的是“暂停模式”。

    来自文档:

    在暂停模式下,您必须显式调用 stream.read() 来获取块 的数据。流以暂停模式开始。

    换句话说:你需要大块数据,你得到它,你使用它,当你准备好时,你请求新的数据块。在这种模式下,您可以控制何时获取数据。

    如何更改为“暂停模式”

    这是此流的默认模式。但是当您注册“数据”事件处理程序时,它会切换到“流动模式”。因此不要使用readstream.on('data',...) 而是在触发时使用readstream.on('readable', function(){...}),这意味着流已准备好提供数据块。要获取大量数据,请使用var chunk = readstream.read();

    来自文档的示例:

    var fs = require('fs');
    var rr = fs.createReadStream('foo.txt');
    rr.on('readable', function() {
      console.log('readable:', rr.read());
    });
    rr.on('end', function() {
      console.log('end');
    });
    

    请阅读文档了解更多详情,因为当流自动切换到“流动模式”时会有更多可能性。

    使用慢速处理程序和流动模式:

    如果您想/需要在“流动模式”下工作,也有解决方案。您可以暂停和恢复流。当你得到块形式 readstream('data') 时,暂停流,当你完成工作时,然后恢复它。

    文档示例:

    var readable = getReadableStreamSomehow();
    readable.on('data', function(chunk) {
      console.log('got %d bytes of data', chunk.length);
      readable.pause();
      console.log('there will be no more data for 1 second');
      setTimeout(function() {
        console.log('now data will start flowing again');
        readable.resume();
      }, 1000);
    });
    

    【讨论】:

    • 第二个示例的行为似乎与我的预期不同。如果我添加“结束”和/或“完成”事件,它们都会在 setTimeout() 函数完成之前触发。如何在直播期间暂停较长时间运行的任务,并可靠地知道它何时结束?
    • 首先,你确定你在“数据”事件中暂停了流吗?再想一想,除了setTimeout中的回调之外,您确定没有在其他地方恢复它吗?第三次检查,您的流可能太小以至于第一个“数据”事件耗尽流并且没有更多数据,这意味着它也触发了完成事件。
    • 是的,据我所知。这是我的帖子:stackoverflow.com/questions/57377295/… 我还设计了一个小得多的版本,基本上就是按照您上面显示的那样做。 "end" 和 "finished" 事件发生在超时结束之前。
    • 在不运行和调试代码的情况下很难进行调查。我根据我看到的情况给了你一些猜测
    • @TsarBomba 我看到的和你描述的一样。我设法通过使用 readableStream 的 readableLength 属性来解决。就我而言,我检查了它是否为 0,以便我可以关闭我的数据库连接。节点版本 12.9 也有 readableEnded
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2017-09-28
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多