这里记录工作中遇到的技术点,以及自己对生活的一些思考,周三或周五发布。
封面图
Node中的streams流
streams流是Node中的最好的特性之一。它在我们的开发过程当中可以帮助我们做很多事情。比如通过流的方式梳理大量数据,或者帮我们分离应用程序。
和streams流相关的内容有哪些呢?大致有这么几点:
- 处理大量数据
- 使用管道方法
- 转换流
- 读写流
- 解耦I/O
保持管道流的活力
通常情况下,当原始流通过管道连接到目标流时,目标流会随着原始流的结束而结束。
有时候我们希望在原始流结束之后额外再去做一些别的操作。这时候怎么办呢,我们来看一个例子:
名字随便起一个,start.js
const net = require('net')
const fs = require('fs')
net
.createServer((socket) => {
const content = fs.createReadStream(__filename)
content.pipe(socket)
content.on('end', () => {
socket.end('\n========Footer=====\n')
})
})
.listen(4000)
复制代码
这时候我们执行
node start.js
复制代码
然后我们发一个请求:
curl http://localhost:4000
复制代码
会发现有报错信息
这是因为:当content可读流结束后,与之连接的socket流也就结束了。这时候,我们想要在socket后面添加内容就不可能了。
我们可以修改我们的代码如下:
const net = require('net')
const fs = require('fs')
net
.createServer((socket) => {
const content = fs.createReadStream(__filename)
content.pipe(socket, { end: false })
content.on('end', () => {
socket.end('=========Footer========')
})
})
.listen(4000)
复制代码
这次我们在content.pipe中加入了第二个参数end:false。这告诉管道方法避免在源流结束时结束目标流,这时候我们的代码就不会报错。
相应的我们可以收到返回的信息:
生产中的管道流
pipe方法是streams流中一个非常重要的特性。它可以让我们把多个流组合成一行代码。
作为Node核心的一部分,它在进程运行时间不太重要的情况下非常有用。比如我们常用的cli工具。
但是不好的一点是它的错误处理。假如管道流中有一个流出现错误,它往往直接取消管道连接,然后将剩余的流进行销毁。这样一来,他们就不会泄露资源,但是有可能会导致内存泄露。
再来看一个例子:
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
fs.createReadStream('veryBigData.file').pipe(res)
})
server.listen(4000)
复制代码
这个服务在用管道给用户返回数据,因此很有可能会产出内存开销以及文件相关的信息泄露。
如果http响应在文件被完全传输给用户之前关闭,文件相关的一些信息肯定会泄露,以及文件流也会产生一些内存开销,文件流也会留在内存中,因为我们没有关闭它。
所以我们需要一些错误处理机制,能够在适当的时候销毁我们管道中的流。
这需要提到另外一个模块儿---pump(泵)。pump专门用来处理这些问题。
pump(泵)
我们再来尝试一个例子:
const fs = require('fs')
const http = require('http')
const pump = require('pump')
const server = http.createServer((req, res) => {
const stream = fs.createReadStream('veryBigData.file')
pump(stream, res, done)
})
function done(err) {
if (err) {
return console.log('文件导流出现错误----', err)
}
console.log('文件导流成功')
}
server.listen(4000)
复制代码
这时候,运行这个文件,发起请求,我们会发现它报错了。
每个传递到pump方法中的流都会被传给下一个流。如果上一个传入的是个函数,pump会在所有流都完成后执行这个方法。
pump内部有些附加的方法。比如关闭,错误处理以及在不影响其他流的情况下关闭另外一个流的方法。
如果其中一个流关闭,其他流将被销毁,并调用传递给pump的回调函数。
当然我们也可以手动去处理这些错误或者在数据关闭时销毁流,比如:
const server = http.createServer((req, res) => {
const stream = fs.createReadStream('veryBigData.file')
stream.pipe(res)
res.on('close',()=>{
stream.destory()
})
})
server.listen(4000)
复制代码
但是通常情况下,使用pump(泵)更加方便,也更加安全。
pumpify
在编写管道的时候,尤其是作为一个单独的模块的时候。我们可能会希望将这些方法导出为外部的用户。这时候怎么办呢?
正如我们之前说的:管道由一系列传输流组成。我们将数据写入管道中的第一个流,然后数据通过它传输,直到写入最后一个流。
我们再看个例子:
const {createGzip} = require('zlib')
const {crateCipher, createCipheriv} = require('crypto')
const pumpify = require('pumpify')
const base64 = require('base64-encode-stream')
function pipeline(){
const stream1 = createGzip()
const stream2 = createCipheriv()
const stream3 = base64()
return pumpify(stream1,stream2,stream3)
}
复制代码
这种写法有点类似Promisify 。
最后
- 公众号《JavaScript高级程序设计》
- 公众号内回复”vue-router“ 或 ”router“即可收到 VueRouter源码分析的文档。
- 回复”vuex“ 或 ”Vuex“即可收到 Vuex 源码分析的文档。
全文完,如果喜欢。
请点赞和"在看"吧,最好也加个"关注",或者分享到朋友圈。