Node.js Stream Pipeline 與 Pipe:安全連結多個串流

當我們學會了如何建立各種類型的串流後,最重要的課題就是:如何安全、優雅且高效地將這些「水管」串接起來?

傳統的 .pipe() 方法

這是最直覺且編寫速度最快的連鎖寫法。它會自動處理資料流速與背壓 (Backpressure) 問題。

readable.pipe(transform).pipe(writable);

致命缺點:資源洩漏風險

雖然 pipe() 很好用,但它不會自動處理錯誤。如果你沒有為鏈結中的「每一個」串流手動掛載 .on('error', ...),一旦中間某個環節出錯,其他的串流可能不會被正確關閉,導致系統檔案描述符 (File Descriptors) 耗盡或記憶體洩漏。

現代解決方案:stream.pipeline()

Node.js 引入了 pipeline() 函式,旨在解決 pipe() 的不安全性。它是目前專案開發中的標準做法

Pipeline 的三大核心優勢:

  1. 集中錯誤處理:只需要在最後一個參數傳入回呼函式,即可捕獲整條鏈上任何位置的錯誤。
  2. 自動資源清理:只要任何一個串流出錯或結束,pipeline() 會自動呼叫 .destroy() 關閉所有相關串流。
  3. 支援多個環節:可以輕鬆串接無限多個 Transform 串流。

實戰範例:安全地壓縮檔案

const { pipeline } = require('stream');
const fs = require('fs');
const zlib = require('zlib');

pipeline(
  fs.createReadStream('very-large-data.bin'),
  zlib.createGzip(),
  fs.createWriteStream('data.bin.gz'),
  (err) => {
    if (err) {
      console.error('串流處理失敗(資源已自動釋放):', err.message);
    } else {
      console.log('數據壓縮並寫入成功!');
    }
  }
);

終極推薦:Async / Await 版本

在 Node.js 15+ 中,官方提供了直接支援 Promise 的版本,讓你可以配合 try...catch 寫出極其乾淨的代碼。

const { pipeline } = require('stream/promises');
const fs = require('fs');

async function safeCopy() {
  try {
    await pipeline(fs.createReadStream('input.txt'), fs.createWriteStream('output.txt'));
    console.log('檔案安全複製完成');
  } catch (err) {
    console.error('複製過程中斷:', err.message);
  }
}

Pipe vs Pipeline 決策表

特性.pipe()stream.pipeline()
自動關閉資源❌ 否✅ 是
錯誤處理須逐個監聽 error集中回呼處理
Promise 支援須自行封裝✅ 內建支援 (stream/promises)
適用場景簡單的臨時腳本正式專案、複雜負載環境

總結

  1. 除非是極簡單的單行指令,否則請一律使用 pipeline()
  2. 結合 stream/promisesasync/await 是目前處理串流最穩健的組合。
  3. 良好的串流管理能確保你的 Node.js 伺服器在長期運行下依然保持極低的資源消耗。