在构建较复杂的系统时,通常将其拆解为功能独立的若干部分。这些部分的接口遵循一定的规范,通过某种方式相连,以共同完成较复杂的任务。譬如,shell通过管道|连接各部分,其输入输出的规范是文本流。
在Node.js中,内置的Stream模块也实现了类似功能,各部分通过.pipe()连接。
鉴于目前国内系统性介绍Stream的文章较少,而越来越多的开源工具都使用了Stream,本系列文章将从以下几方面来介绍相关内容:
流的四种类型
Stream提供了以下四种类型的流:
使用Stream可实现数据的流式处理,如:
创建可读流。
实例:流式消耗迭代器中的数据。
实际使用时,new ToReadable(iterable)会返回一个可读流,下游可以流式的消耗迭代器中的数据。
执行上述代码,将会有100亿个随机数源源不断地写进标准输出流。
创建可读流时,需要继承Readable,并实现_read方法。
可以通过监听data事件的方式消耗可读流。
上面的例子中,process.stdout代表标准输出流,实际是一个可写流。下小节中介绍可写流的用法。
创建可写流。
前面通过继承的方式去创建一类可读流,这种方法也适用于创建一类可写流,只是需要实现的是_write(data, enc, next)方法,而不是_read()方法。
有些简单的情况下不需要创建一类流,而只是一个流对象,可以用如下方式去做:
创建可读可写流。
Duplex实际上就是继承了Readable和Writable的一类流。
所以,一个Duplex对象既可当成可读流来使用(需要实现_read方法),也可当成可写流来使用(需要实现_write方法)。
上面的代码中实现了_read方法,所以可以监听data事件来消耗Duplex产生的数据。
同时,又实现了_write方法,可作为下游去消耗数据。
因为它既可读又可写,所以称它有两端:可写端和可读端。
可写端的接口与Writable一致,作为下游来使用;可读端的接口与Readable一致,作为上游来使用。
在上面的例子中,可读流中的数据(0, 1)与可写流中的数据('a', 'b')是隔离开的,但在Transform中可写端写入的数据经变换后会自动添加到可读端。
Tranform继承自Duplex,并已经实现了_read和_write方法,同时要求用户实现一个_transform方法。
前面几节的例子中,经常看到调用data.toString()。这个toString()的调用是必需的吗?
本节介绍完如何控制流中的数据类型后,自然就有了答案。
在shell中,用管道(|)连接上下游。上游输出的是文本流(标准输出流),下游输入的也是文本流(标准输入流)。在本文介绍的流中,默认也是如此。
对于可读流来说,push(data)时,data只能是String或Buffer类型,而消耗时data事件输出的数据都是Buffer类型。对于可写流来说,write(data)时,data只能是String或Buffer类型,_write(data)调用时传进来的data都是Buffer类型。
也就是说,流中的数据默认情况下都是Buffer类型。产生的数据一放入流中,便转成Buffer被消耗;写入的数据在传给底层写逻辑时,也被转成Buffer类型。
但每个构造函数都接收一个配置对象,有一个objectMode的选项,一旦设置为true,就能出现“种瓜得瓜,种豆得豆”的效果。
Readable未设置objectMode时:
输出:
Readable设置objectMode后:
可见,设置objectMode后,push(data)的数据被原样地输出了。此时,可以生产任意类型的数据。
预告
Stream系列共三篇文章:
参考文献