前言
在編寫代碼時,我們應該有一些方法將程序像連接水管一樣連接起來 -- 當我們需要獲取一些數據時,可以去通過"擰"其他的部分來達到目的。這也應該是IO應有的方式。 -- Doug McIlroy. October 11, 1964
本質上來說,編碼就是對數據的讀取,處理最后返回結果,數據在一個程序又一個程序中不斷傳遞。理想情況下,數據的傳遞應該是不停滯的,但是現實情況中因為諸如單個數據過大,內存較小,IO處理較慢等客觀原因使數據不能流暢的流動
起來。這時我們就需要一種方法去將數據拆分成一小塊一小塊的數據(chunks),流水一樣的讀取處理寫入。這種方法便是流(stream)
,nodejs中的流主要分為以下四種:
- Readable(可讀流)
- Writable(可寫流)
- Duplex(可讀寫流)
- Transform (變換流)
而所有的流都有一種公有方法,它會像管道一樣處理流的數據,這便是pipe
,下面一張大神制作的圖很好的演示了這個過程:
Markdown
正文
pipe期望的使用方式
pipe
函數需要將源頭src
并將數據輸出到一個可寫的流dst
中:
src.pipe(dst)
pipe
將會返回dst
,所以我們希望可以鏈式調用將多個流用管道連接起來:
a.pipe(b).pipe(c).pipe(d)
pipe的簡化源碼分析
為了實現以上期望的使用方式,我們將Nodejs源碼簡化一下看是怎樣實現的
stream.prototype.pipe = function(dest, options) {
this.on('data', (chunk) => {
if (dest.writable) {
if (false === dest.write(chunk) && this.pause) {
this.pause();
}
}
});
dest.on('drain', () => {
this.resume();
});
dest.emit('pipe', this);
return dest;
};
以上代碼主要做了4件事:
- 可讀流監聽
data
事件
1)首先判斷dest.Writable
,當寫完時會賦值為false
2)此時如果消費者消費速度慢,這時產生了一個現象,叫做背壓
。背壓問題即是外部的生產者和消費者速度差造成的,此時我們需要暫停寫入.pause()
- 可寫流監聽
drain
事件
1)消費者完成消費,可以觸發drain
事件,此時我們可以繼續向流中寫入數據。執行.resume()
- 觸發
pipe
事件,通知有流寫入 - 返回
dest
流,可以進行鏈式調用
小記
簡單的梳理了下stream pipe的原理和實現方式。在現實應用中諸如gulp,Browserify等工作流工具都使用了pipe。而stream更是應用廣泛,文件、http、打包壓縮甚至console等都是用流實現的。