Node Stream pipe的誕生

PlayerWho發表於2018-01-25

流在Node.js中是處理流資料的抽象介面。stream模組提供了基礎的API。使用這些API可以很容易地來構建實現流介面的物件。 Node.js裡提供了很多流物件。例如http.IncomingMessage類、fs.createReadStream類等等。都繼承流的私有屬性和公有方法。 所以學習流,有助於學習Node的其他模組。

文章結構

  • 簡介stream
  • pipe原始碼

stream

const EE = require('events');
const util = require('util');
function Stream() {
  EE.call(this);
}
util.inherits(Stream, EE);
複製程式碼
  • Stream繼承EventEmitter。流可以是可讀的、可寫的,或是可讀寫的。
  • Stream分為Readable(可讀流)、Writable(可寫流)、Duplex(可讀寫流)、Transform(讀寫過程中可以修改和變換資料的 Duplex 流)。

pipe

Stream.prototype.pipe = function(dest, options){
    var source = this;
    source.on('data', ondata);
    dest.on('drain', ondrain);
    if (!dest._isStdio && (!options || options.end !== false)) {
        source.on('end', onend);
        source.on('close', onclose);
    }
    source.on('error', onerror);
    dest.on('error', onerror);
    source.on('end', cleanup);
    source.on('close', cleanup);  
    dest.on('close', cleanup);
    dest.emit('pipe', source);
    return dest;
};
複製程式碼
  • Stream公有方法pipe
    • source是可讀流:Readable。dest是可寫流:Writable。
    • Readable.pipe(Writable)
    • Readable訂閱事件:data、error、end、close。Readable接收到事件執行相應的方法。
    • Writable訂閱事件:drain、error、close,併發布pipe事件。Writable接收到事件執行相應的方法。
    • 返回Writable。

ondata

function ondata(chunk) {
    if (dest.Writable) {
      if (false === dest.write(chunk) && source.pause) {
        source.pause();
      }
    }
  }
複製程式碼
  • Readable訂閱data事件。
  • Readable觸發data事件,表示讀入資料。
  • dest.Writable當寫完時會賦值為false。
  • 如果讀的太快,沒有寫完dest.write(chunk)返回false。
  • source.pause暫停寫入。
  • 總結:訂閱data事件,觸發ondata方法,如果Readable讀入資料太快,來不及寫入,要暫停讀入資料。

ondrain

function ondrain() {
    if (source.Readable && source.resume) {
      source.resume();
    }
  }
複製程式碼
  • Writable訂閱drain事件。
  • Writable觸發drain事件,表示這時才可以繼續向流中寫入資料。
  • source.Readable在讀到末尾時會賦值為false。
  • source.resume()表示會重新觸發Writable的data事件。
  • 總結:訂閱drain事件,表示這時才可以繼續向流中寫入資料,呼叫source.resume(),觸發Writable的data事件。

cleanup

 function cleanup() {
    source.removeListener('data', ondata);
    dest.removeListener('drain', ondrain);

    source.removeListener('end', onend);
    source.removeListener('close', onclose);

    source.removeListener('error', onerror);
    dest.removeListener('error', onerror);

    source.removeListener('end', cleanup);
    source.removeListener('close', cleanup);

    dest.removeListener('close', cleanup);
  }

複製程式碼
  • 移除Writable和Readable訂閱的事件。

error

function onerror(er) {
    cleanup();
    if (EE.listenerCount(this, 'error') === 0) {
      throw er; // Unhandled stream error in pipe.
    }
  }
複製程式碼
  • Writable和Readable有錯誤是執行的方法。

options

if (!dest._isStdio && (!options || options.end !== false)) {
    source.on('end', onend);
    source.on('close', onclose);
}
複製程式碼
  • dest._isStdio 暫時沒理解。
  • 如果不傳options或者options.end不是false,給Readable訂閱end和close事件。

onend

var didOnEnd = false;
  function onend() {
    if (didOnEnd) return;
    didOnEnd = true;

    dest.end();
  }
複製程式碼
  • 當Readable觸發end事件時,執行dest.end(),停止寫入。

onclose

function onclose() {
    if (didOnEnd) return;
    didOnEnd = true;

    if (typeof dest.destroy === 'function') dest.destroy();
  }

複製程式碼
  • 當Readable觸發close事件後,該流將不會再觸發任何事件。
  • dest.destroy()摧毀這個流,併發出傳過來的錯誤。當這個函式被呼叫後,這個寫入流就結束了。
小記
  • pipe方法大白話:讀東西,寫東西,讀快了,來不及寫,暫停讀,來得及寫了,再讀東西,再寫。。。
  • 流在Node.js中應用廣泛,http、檔案、打包工具等等。可見流的重要性。
  • 學習原始碼,有助於理解底層的實現。
參考

相關文章