Node.js Writable Stream:將數據寫入目標端

Writable Stream (可寫入串流) 是資料的終點,讓你能夠將數據塊 (Chunks) 寫入特定的目標,常見的例子包括寫入實體檔案 (fs.createWriteStream)、HTTP 回應物件 (res),或是標準輸出 process.stdout

基礎寫入邏輯:write() 與 end()

使用 Writable Stream 時,核心流程是持續呼叫 write(),並在完成時呼叫 end() 釋放資源。

const fs = require('fs');
const writer = fs.createWriteStream('notes.txt');

// 寫入數據塊
writer.write('第一行日誌...\n');
writer.write('第二行數據...\n');

// 告知串流已完成,並可選擇性寫入最後一塊數據
writer.end('寫入結束!');

writer.on('finish', () => {
  console.log('所有數據已成功寫入硬碟。');
});

進階概念:背壓 (Backpressure) 與 drain 事件

這是 Writable Stream 效能管理的靈魂。如果你的寫入速度快於底層儲存裝置(如硬碟)的處理速度,數據會不斷堆積在 Node.js 的內部緩衝區中。

  • 當緩衝區爆滿(達到 highWaterMark)時,writer.write() 會回傳 false
  • 此時你應該停止寫入,並監聽 drain 事件。
  • drain 事件觸發時,代表緩衝區已清空,可以繼續餵資料。
function heavyWrite(writer, data) {
  let i = 1000;
  function write() {
    let ok = true;
    while (i > 0 && ok) {
      i--;
      if (i === 0) {
        writer.end('最後一塊');
      } else {
        ok = writer.write(data); // 檢查緩衝區是否已滿
      }
    }
    if (i > 0) {
      // 緩衝區滿了,暫停一下,等 drain 再繼續
      writer.once('drain', write);
    }
  }
  write();
}

批次效能優化:cork() 與 uncork()

如果你需要連續寫入許多細小的資料塊,頻繁與作業系統通訊會拖慢效能。你可以使用 cork() 將寫入操作暫時快取在記憶體中,最後再一次性透過 uncork() 送出。

writer.cork();
writer.write('一部分...');
writer.write('另一部分...');
writer.write('更多部分...');
process.nextTick(() => writer.uncork());

核心事件總覽

  • drain:緩衝區清空,可恢復寫入。
  • finish:呼叫 end() 且所有排隊數據皆已寫入完成。
  • pipe:當有 Readable 接入時觸發。
  • unpipe:當 Readable 離線時觸發。
  • error:寫入過程發生任何異常。
專業提示:在大多數情況下,手動處理 write()drain 是很繁瑣且容易出錯的。請盡量使用 pipe()pipeline(),它們會自動幫你完美處理背壓問題。

總結

  1. Writable 是資料的接收端。
  2. 務必檢查 write() 的回傳值,以避免記憶體溢漏。
  3. end() 呼叫後,該串流將無法再次寫入。