stream pipe的原理及簡化原始碼分析

akirastorm發表於2018-02-03

前言

在編寫程式碼時,我們應該有一些方法將程式像連線水管一樣連線起來 -- 當我們需要獲取一些資料時,可以去通過"擰"其他的部分來達到目的。這也應該是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等都是用流實現的。

參考資料

相關文章