简体   繁体   English

如何在 node.js 可读流中调用异步函数

[英]How to call an asynchronous function inside a node.js readable stream

This is a short example of the implementation of a custom readable stream.这是自定义可读流的实现的简短示例。 The class is called MyStream.该类称为 MyStream。 The stream gets the file/foldernames out of a directory and pushes the values to the data-event.流从目录中获取文件/文件夹名并将值推送到数据事件。

To compare I implemented (in this example) two different ways/functions.为了比较我实现(在这个例子中)两种不同的方式/功能。 One is syncronous and the other is asynchronous.一个是同步的,另一个是异步的。 The second argument of the constructor lets you decide, which way is used (true for the asynchronous and false for synchronous.构造函数的第二个参数让你决定使用哪种方式(异步为真,同步为假。

The readcounter counts the number of times the method _read is called. readcounter 计算方法 _read 被调用的次数。 Just to give a feedback.只是为了提供反馈。

var Readable = require('stream').Readable;
var util = require('util');
var fs = require('fs');
util.inherits(MyStream, Readable);

function MyStream(dirpath, async, opt) {
  Readable.call(this, opt);
  this.async = async;
  this.dirpath = dirpath;
  this.counter = 0;
  this.readcounter = 0;
}

MyStream.prototype._read = function() {
  this.readcounter++;
  if (this.async === true){
    console.log("Readcounter: " + this.readcounter);
    that = this;
    fs.readdir(this.dirpath,function(err, files){
      that.counter ++;
      console.log("Counter: " + that.counter);
      for (var i = 0; i < files.length; i++){
        that.push(files[i]);
      }
      that.push(null);
    });
  } else {
    console.log("Readcounter: " + this.readcounter);
    files = fs.readdirSync(this.dirpath)
    for (var i = 0; i < files.length; i++){
      this.push(files[i]);
    };
    this.push(null);
  }
};
//Instance for a asynchronous call
mystream = new MyStream('C:\\Users', true);
mystream.on('data', function(chunk){
  console.log(chunk.toString());
});

The synchronous way works like expected, but something interesting is happening, when I call it asynchronously.同步方式按预期工作,但是当我异步调用它时,发生了一些有趣的事情。 Everytime the filename is pushed via that.push(files[i]) the _read method is called again.每次通过that.push(files[i])推送文件名时,都会再次调用 _read 方法。 Which causes errors, when the first asynchronous loop is finished and that.push(null) defines the end of the stream.当第一个异步循环完成并且that.push(null)定义流的结尾时,这会导致错误。

The enviroment I am using to test this: node 4.1.1, Electron 0.35.2.我用来测试的环境:节点 4.1.1,电子 0.35.2。

I do not understand why _read is called so ofthen and why this is happening.我不明白为什么 _read 被如此频繁地调用以及为什么会发生这种情况。 Maybe it is a bug?也许这是一个错误? Or is there somthing I do not see at the moment.或者有什么我现在看不到的东西。 Is there a way to build a readable stream by using asynchronous functions?有没有办法使用异步函数构建可读流? To push the chunks asynchronously would be really cool, because it would be the non blocking stream way.异步推送块会非常酷,因为它是非阻塞流方式。 Specially when you have bigger amount of data.特别是当您有大量数据时。

_read is called whenever the "reader" needs data and it usually happens just after you push data. _read在“读者”需要数据时被调用,它通常在您推送数据之后发生。

I had the same sort of "issues" with implementing _read directly so now, I write a function returning a stream object.我在直接实现_read遇到了同样的“问题”,所以现在我编写了一个返回流对象的函数。 It works quite good and data can't be "pulled" from my stream, data is avalaible/pushed when I decide it.它工作得很好,无法从我的流中“拉出”数据,当我决定数据时,数据是可用的/推送的。 With your example, I would do it like this:用你的例子,我会这样做:

var Readable = require('stream').Readable;
var fs = require('fs');

function MyStream(dirpath, async, opt) {
  var rs = new Readable();
  // needed to avoid "Not implemented" exception
  rs._read = function() { 
    // console.log('give me data!'); // << this will print after every console.log(folder);
  };

  var counter = 0;
  var readcounter = 0;

  if (async) {
    console.log("Readcounter: " + readcounter);
    fs.readdir(dirpath, function (err, files) {
      counter++;
      console.log("Counter: " + counter);
      for (var i = 0; i < files.length; i++) {
        rs.push(files[i]);
      }
      rs.push(null);
    });
  } else {
    console.log("Readcounter: " + readcounter);
    files = fs.readdirSync(dirpath)
    for (var i = 0; i < files.length; i++) {
      rs.push(files[i]);
    };
    rs.push(null);
  }

  return rs;
}

var mystream = MyStream('C:\\Users', true);
mystream.on('data', function (chunk) {
  console.log(chunk.toString());
});

It doesn't directly answer your question but it's a way to get a working code.它不会直接回答您的问题,但它是获取工作代码的一种方式。

Fixed since Node 10从节点 10 开始修复

https://github.com/nodejs/node/issues/3203 https://github.com/nodejs/node/issues/3203

If my understanding is correct, before Node 10, async _read() implementation had to call this.push() only once with data and create their own buffer in order to delay following this.push() to the next _read() call.如果我的理解是正确的,在 Node 10 之前,异步_read()实现只需要使用数据调用this.push()一次并创建自己的缓冲区,以便将this.push()延迟到下一个_read()调用。

const {Readable} = require('stream');
let i = 0;
const content_length = 5;
let content_read = 0;

const stream = new Readable({
  encoding: 'utf8',
  read() {
    console.log('read', ++i);
    const icopy = i;
    setTimeout(() => {
      for (let a=1; a<=3; a++) {
        this.push(icopy+':'+a);
      }
      content_read++;
      if (content_read == content_length) {
        console.log('close');
        this.push(null);
      }
    }, Math.floor(Math.random()*1000));
  },
});

stream.on('data', (data) => {
  console.log(data);
});

Node 8.17.0 :节点 8.17.0 :

read 1
1:1
read 2
1:2
read 3
1:3
read 4
2:1
read 5
2:2
read 6
2:3
read 7
6:1
read 8
6:2
read 9
6:3
read 10
9:1
read 11
9:2
read 12
9:3
read 13
12:1
read 14
12:2
read 15
12:3
read 16
close
events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: stream.push() after EOF

Node 10.24.1:节点 10.24.1:

read 1
1:1
1:2
1:3
read 2
2:1
2:2
2:3
read 3
3:1
3:2
3:3
read 4
4:1
4:2
4:3
read 5
5:1
5:2
5:3
close

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM