第七十九期:Node中的streams流(读写流)

120 阅读4分钟

这里记录工作中遇到的技术点,以及自己对生活的一些思考,周三或周五发布。

封面图

Node中的streams流

streams流是Node中的最好的特性之一。它在我们的开发过程当中可以帮助我们做很多事情。比如通过流的方式梳理大量数据,或者帮我们分离应用程序。

和streams流相关的内容有哪些呢?大致有这么几点:

  • 处理大量数据
  • 使用管道方法
  • 转换流
  • 读写流
  • 解耦I/O

读写流

可读流可以帮助我们做一些非常有意思的事情。比如可以读取不适合内存的数据,以及可以用来处理数据量特别大的数据。而可写流则可以用来操作输出,比如可以用来控制硬件,或者将数据推送过去,进行批量处理。

这里我们需要用到两个模块儿,from2 和 to2。

还是举个例子:

const from = require('from2')
const to = require('to2')

const rs = from(() => {
  rs.push(Buffer.from('Hello, my name is terrence'))
  rs.push(null)
})

rs.on('data', (data) => {
  console.log(data.toString())
})

我们可以看到终端打印出来的信息。

但是这个结果是通过监听data事件后打印的。我们可以用可写流进行实现:

const from = require('from2')
const to = require('to2')

const rs = from(() => {
  rs.push(Buffer.from('Hello, my name is terrence'))
  rs.push(null)
})

//rs.on('data', (data) => {
//  console.log(data.toString())
//})


const ws = to((data, enc, cb) => {
  console.log('你写的是:' + data.toString())
  cb()
})

rs.pipe(ws)

然后重新执行,我们仍旧可以看到结果:

这是为什么

from2模块基于stream.Readable的构造器进行了一层包装,并且为我们创建了一个流。而且它还有一个好处是,它有一个destory方法可以自由的释放流资源,并且具有异步推送的功能。

同时,和之前提到过的through2模块儿类似。from2和to2模块都具有对象模式,可以很方便的创建对象流。

当我们通过pipe将rs可读流送入ws可写流的时候,rs通过push方法推入了我们想要输出的字符串。然后可写流接收到数据,并且cb()方法指明下一段数据已经standby。rs推入null,表明这个流已经结束了,通过管道,ws可写流也触发结束事件。

大体是这么个过程。

核心模块的读写流

如果我们想要实现自己的读写流,我们可能需要基于stream.Readable构造器进行扩展。

这个基类会调用一个_read方法。关键的地方就在于我们如何去实现这个_read方法。我们也可以在options参数对象中提供一个read属性,当然它是个方法,这个方法将被添加为实例的_read方法。

不管我们什么时候调用read方法,steam总是会需要我们提供一些数据供stream进行消费。我们可以通过push方法给他推入一些数据。

举个例子:

const { Readable, Writable } = require('readable-stream')
const rs = Readable({
  read: () => {
    rs.push(Buffer.from('terrence'))
    rs.push(null)
  },
})
const ws = Writable({
  write: (data, enc, cb) => {
    console.log(`很好,知道你是`, data.toString())
    cb()
  },
})

rs.pipe(ws)

这样,我们仍然可以实现上面的效果。

核心可读流流量控制问题

可读流上的_read方法不接受回调。由于stream流通常包含不止一个数据缓冲区,因此流需要多次调用_read方法。

这就需要等待调用push,然后在流的内部缓冲区有可用空间时再次调用_read方法。如果我们想以异步方式多次调用push,这就会出现一个问题。

比如:

const { Readable, Writable } = require('readable-stream')
const rs = Readable({
  read: () => {
    setTimeout(() => {
      rs.push(Buffer.from('terrence'))
      setTimeout(() => {
        rs.push(Buffer.from('apple'))
      }, 500)
    }, 1000)

    // rs.push(null)
  },
})
rs.on('data',(data)=>{
  console.log('姓名:',data.toString())
})

上面的代码,我们希望能够交替打印terrence和apple。但是实际情况如下:

但是,通过之前我们提到的from模块,我们可以解决这个问题

const from = require('from2')
const rs = from((size, cb) => {
  setTimeout(() => {
    rs.push(Buffer.from('terrence'))
    setTimeout(() => {
      rs.push(Buffer.from('apple'))
      cb()
    }, 500)
  }, 1000)
})
rs.on('data', (data) => {
  console.log('姓名:', data.toString())
})

再次打印,会发现一切正常。

最后

  • 公众号《JavaScript高级程序设计》
  • 公众号内回复”vue-router“ 或 ”router“即可收到 VueRouter源码分析的文档。
  • 回复”vuex“ 或 ”Vuex“即可收到 Vuex 源码分析的文档。

全文完,如果喜欢。

请“点赞”和"在看"吧,最好也加个"关注",或者分享到朋友圈。