深入浅出了解Node.js Streams

2020-06-17 05:50:49易采站长站整理

使用一个例子来展示流的使用

首先利用如下脚本创建一个比较大的文件(大概 430 MB)


const fs = require('fs');
const file = fs.createWriteStream('test.md');
for(let i=0; i<= 1e6; i++) {
file.write('hello world.n');
}
file.end();

在当前目录下,启动 http 服务


const http = require('http')
const fs = require('fs')
const server = http.createServer(function (req, res) {
fs.readFile(__dirname + '/test.md', (err, data) => {
res.end(data)
})
})
server.listen(3000)

得到的结果,如图


const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const stream = fs.createReadStream(__dirname + '/test.md')
stream.pipe(res)
})
server.listen(3000)

时间减少了 2s 多。这可以解释为,在读取文件内容,并且不需要改变内容的场景下,流能够完成只读取 buffer,然后直接传输,不做额外的转换,避免损耗,提高性能。
上述代码中,应用了 stream.pipe(…) 。它主要是对流进行链式地管道操作,例如


src.pipe(dest1).pipe(dest2)

这样数据流会被自动管理。

如果可读流发生错误,目标可写流不会自动关闭,需要手动关闭所有流以避免内存泄漏。

通常,当你使用 pipe 方法时,就不需要使用事件,但如果场景需要以更灵活、自定义的方式使用流,那么就要考虑事件。

Stream events

在上述例子中,我们使用了可读流的 data 、end 事件来控制文件的读取,它本质上与 pipe 方法相同,例如


# readable.pipe(writable)
readable.on('data', (chunk) => {
writable.write(chunk);
});
readable.on('end', () => {
writable.end();
});

只不过,使用 event 会更加灵活,可控。

图中简单罗列了可读流、可写流的相关事件、方法,其中最重要的是

可读流:

data 事件:每当流将一大块数据传递时,就会触发;
end 事件:当没有更多数据要从流发出时,就会触发。

可写流:

drain 事件:当可以继续写入数据到流时会触发事件;
finish 事件:处理完全部数据块之后触发。

流的不同类型

除了上面涉及到的可读、写流之后,还有 Duplex、Transform 两类: