Node.js Stream ondata触发时机与顺序的探索

2020-06-17 05:47:54易采站长站整理

awaitDrain++
执行两次是作者不希望看到的情况,因为下游触发drain事件时
awaitDrain
相应减1,直到其值为0时才让上游重新流动,如果
awaitDrain++
执行两次,下游却只触发一次drain事件,
awaitDrain
就不会为0,上游不重新流动也就无法继续读取数据。

真相的探索过程

虽然从理性上认为

increasedAwaitDrain
没起到作用,但也无法肯定加绝对,自己尝试去求助,没有出现高手指点出问题所在,但一个同事听我描述后,说可能这就是个BUG,虽心中觉得可能性不大,但还是抱着试试看的心态切换到master分支上去瞅瞅,随即发现最新的代码里并没有与
increasedAwaitDrain
类似的逻辑,间接说明v8.11.1分支上
increasedAwaitDrain
相关逻辑的确无用。

虽然比较肯定这里存在一段无用代码,但应该如何理解作者在

increasedAwaitDrain
上方的注释呢?为了进一步揭露真相,自己继续花时间去看了看stream.Readable相关代码,想知道data事件的触发时机与顺序是如何决定的。

readable流的简单原理

在进一步解释data事件的触发顺序前,简单讲一下readable流的实现原理,如果需要自己实现一个readable流,可以使用new stream.Readable(options)方法,其中options可包含四个属性:highWaterMark、encoding、objectMode、read。最主要的是read属性,当流的使用者需要数据时,read方法被用来从数据源获取数据,然后通过

this.push(chunk)
将数据传递给使用者,如果没有更多数据可供读取时使用
this.push(null)
表示读取结束。


const Readable = require('stream').Readable;
let letter = 'ABCDEFG'.split('');
let index = 0;
const rs = new Readable({
read(size) {
this.push(letter[index++] || null);
}
});
rs.on('data', chunk => {
console.log(chunk.toString());
});
// 输出
// A
// B
// C
// ...

这里ondata虽然没有明显调用read方法,但内部依旧是通过调用read方法结合this.push输出数据,并且在源代码内部可以发现通过参数传递的read方法实际上被赋值给this._read,然后在Readable.prototype.read中调用this._read获取数据。

灵魂代码

为了进一步说明stream.Readable的data事件触发顺序与场景,将有关官方源码经过修改和删减成如下: