浅析Node.js的Stream模块中的Readable对象
我一直都很不愿意扯nodejs的流,因为从第一次看到它我就觉得它的设计实在是太恶心了。但是没办法,Stream规范尚未普及,而且确实有很多东西都依赖了nodejs的流来实现的,所以我也只能捏着鼻子硬着头皮来扯一扯这又臭又硬的nodejs流对象了。
nodejs自带了一个叫stream的模块,引入它便可以得到一组流对象构造器。现在我只说最简单的stream.Readable。
其实用过nodejs的几乎都接触过Readable的实例,只是平时没太在意而已。一个非常典型的例子,http模块中我们处理每个请求时都会有req和res对象,req其实就是一个Readable对象。我们可以在这个req上以流的形式读到HTTP请求的实体部分。
那么问题来了,为什么http模块要在此处以流的方式设计呢?或者从另一个维度来问这个问题就是「nodejs如果获取POST请求的内容?」。懂得用搜索引擎的同学肯定可以很容易地找到这么一个答案:监听data事件收集数据,在end事件中把收集到的数据合并起来。是的,这是解决这个问题的方法。但是为什么它如此设计呢?像PHP那样直接就可以取到POST内容多好?其实这么设计是有好处的,如果我们接收到的数据是非法的,我可以马上察觉,然后响应并断开连接。这样可以避免一些不必要的传输成本。比如上传图片,也许用户错误地选择了一个很大的可执行文件,我们不需要等到这个文件完全上传完毕,只要一个文件头部的若干字节就能判断一个文件是否是图片了。此处使用流的设计就可以先读出前面的几个字节来使用。
上面提到的data事件和end事件都是Readable的事件,这两个事件分别表示收到数据和数据接收完毕。所以其实我们早已知道了Readable的用法,只是很多人不知道它是Readable对象而已。
但是上面这两个事件仅仅是对Readable的消费者而言的事件。内部是如何把一个数据推送到Readable对象里面让Readable触发出这些事件的呢?那么它就是push方法。下面是一个例子,它创建了一个Readable对象,这个对象会流出一个递增的数字(这里使用了babel-node)
importstreamfrom'stream'; varr=newstream.Readable; r.on('data',data=>{ console.log(data+''); }); r.on('end',data=>{ console.log('end'); }); r._read=()=>{ //console.log('beforeread'); }; voidfunctioncallee(i){ if(i<10){ r.push(i+'');//只能传入字符串或Buffre对象 }else{ r.push(null);//当输入一个null时表示流传输完成,触发end事件 } setTimeout(callee,500,i+1); }(0);
如果仔细看上面代码就会发现一个很神奇的地方,这个代码覆写了_read方法,这是什么鬼?其实我也觉得这是个坑,这个私有命名风格就不吐槽了,为何非要覆写这个方法才算实现它?如果没有覆写这个方法,那么在调用push时将抛出异常:
Error:notimplemented atReadable._read(_stream_readable.js:464:22) atReadable.read(_stream_readable.js:341:10)
以上这些便是Readable对象的基本用法。但是还有更多坑会踩到,这篇文章只是一个最简单的介绍,让大家学会如何造出一个能输出数据的Readable对象而已。至于一些read之类的基本方法,反正这些也是不科学的设计之一。