通常,Nodejs的Stream处理是异步的,这意味着,
(1)你不能连续写2条语句:buf = input,read(); output.write(buf)。
为什么?前一句read是异步的,意味着语句执行完之后,实际上根本可能还没IO ready,没有获取到数据呢!——当然,异步IO是基于callback的,或者promise的。我这里是为了叙述的方便。
(2)所以,对异步IO,基于callback的写法应该是:
input.read( (buf)=> { output.write(buf, ()=>{console.log("1 buf copy done!")}), })
或者Promise化的语法:
input.read().then( (buf)=> output.write(buf); )
Promise看起来像是个语法糖,但它与callback有本质的不同:callback的回调过程仍然是同步的,这意味着在深度嵌套callback的过程中,栈有可能溢出。
而对Promise而言,Promise的then仅仅代表向JS语言的执行环境状态机注册一个state change的callback。一个Promise可以串起多个then,但这些then每个都是单独从核心event loop直接dispatch的,因此不存在栈溢出的问题。
进一步的语法糖就是把Promise转换为ES7 async/await的写法。实际上是把Promise转换为了Future对象,当需要从Future对象实际get原始类型数据的时候,就会陷入IO-blocking wait状态了。不过这个IO-blocking wait的内部执行又是一个coroutine的context switch而已。实际上JS主线程并没有阻塞!
(3)但是注意一个问题:(2)的写法只能处理一次buf copy。假如我想连续发起多个buf copy呢?不行,做不到。
而且,对于Node.js的IO模型来说,IO buf的读写操作有可能不能一次性完成,可能拆分成多次完成,也有可能把连续多个小的读写操作合并到一个batch里面执行。
(4)这就意味着:异步IO模型无法完成下面的事情:
IO clone:即将一个input里的数据流同时送到两个output。(Node.js内置的input.pipe(output)只能处理1对1的情况)
(5)假如我想在input pipe到output的过程中,对数据流进行修改怎么办?Node.js提供了一种特殊的Filter Stream,即Transform Stream。
见名以知义,这种Transform必须是流式处理的。假如数据流的底层是固定的blocks,那还好办,但假如是一个很长的JSON字符串,而我们需要即时地修改JSON字符串中的某些value时,麻烦就来了:
需要实现一个CFG parser!普通的FSM可能都不行。而且这个CFG parser必须是流式的处理风格,意味着它需要逐个char逐个char的feed in。而不是一次性传给它整个完整的Buffer。
当然,对简单的处理来说,Transform可以一直在input read in的过程中,将数据缓存到一个大的Buffer对象,然后等待input read的finish事件,此时就可以把整个Buffer对象作为一个完整的JSON字符串来parse->modify->re-serialize的处理了。只不过有个疑问:在这个过程中,input本来期望是与output pipe的,结果实际却是input的read请求被满足了,output的write需要却一直被忽略,这有没有可能造成在output流上触发一个write timeout异常呢???