NPM酷库,每天两分钟,了解一个流行NPM库。

做下载等功能时候,我们经常会使用数据流模块(stream),因为,在大文件下载场景下,如果使用fs.readFile()接口将文件全部读入内存然后再返回给客户端,很容易撑爆内存,比如一个文件200M,同时有100人在下载,那么服务就需要占用10G 内存。

而使用stream,我们不会将文件全部读入内存,而只是在内存中建立一个“水管”,所以内存中不会堆积过多数据:

ctx.body = fs.createReadStream('filename.ext');

上述代码中,我们使用fs.createReadStream()方法创建了一个可读流,Koa会直接读取数据流,并返回给客户端。

双向流

如果一个流同时可读、可写,那么这就是一个双向流。“水管”不适合解释双向流,我们用“电话线”来解释,你说的话(写数据)对方能听见(读数据),同时,对方说话(写数据)你也能听见(读数据)。

妙用双向流在很多时候能解决很复杂的问题,比如,数据库导出场景。因为数据库里数据巨大,所以不能一下子读到Node.js内存中,我们可以创建一个只写流,从数据库逐条读出数据然后调用只写流,将数据写入磁盘,所有数据全部导出到磁盘后,再参照上文创建一个只读流,再将文件返回给客户端。但是这样存在问题的:

  • 对磁盘大小有要求
  • 在数据库导出到磁盘的过程中,客户端接受不到任何信息,很可能会造成浏览器超时访问
  • 白白消耗系统性能,浪费磁盘IO

接下来我们就用双向流解决这个问题:

through

使用 through 可以快速创建一个双向流,相比自己调用stream模块创建双向流,through更方便,因为through已经封装了资源回收等机制。

const through = require('through');

// ...

const stream = through();
ctx.body = stream;

Order.find()
  .cursor()
  .eachAsync(async(order)=>{
    stream.write(order.toJSON());
  })
  .then(()=>{
    stream.end();
  });

上述代码中,首先创建了一个双向流,返回给Koa,然后以Mongoose模型举例,查询数据库,并使用查询游标(cursor)逐条向流中写数据。在流的另一端,Koa就能逐条读出数据并返回给客户端。

参考资料

https://github.com/dominictar...

https://nodejs.org/api/stream...

欢迎关注公众号:梁兴臣

梁兴臣

每天了解一个NPM库,一年后成为Node.js高手


脉冲云_梁兴臣
616 声望194 粉丝

脉冲云CTO,JS全栈开发,DevOps实施,开发体验优化,开发效率提升