Node.js Stream Pipeline 與 Pipe:安全連結多個串流
當我們學會了如何建立各種類型的串流後,最重要的課題就是:如何安全、優雅且高效地將這些「水管」串接起來?
傳統的 .pipe() 方法
這是最直覺且編寫速度最快的連鎖寫法。它會自動處理資料流速與背壓 (Backpressure) 問題。
readable.pipe(transform).pipe(writable);
致命缺點:資源洩漏風險
雖然 pipe() 很好用,但它不會自動處理錯誤。如果你沒有為鏈結中的「每一個」串流手動掛載 .on('error', ...),一旦中間某個環節出錯,其他的串流可能不會被正確關閉,導致系統檔案描述符 (File Descriptors) 耗盡或記憶體洩漏。
現代解決方案:stream.pipeline()
Node.js 引入了 pipeline() 函式,旨在解決 pipe() 的不安全性。它是目前專案開發中的標準做法。
Pipeline 的三大核心優勢:
- 集中錯誤處理:只需要在最後一個參數傳入回呼函式,即可捕獲整條鏈上任何位置的錯誤。
- 自動資源清理:只要任何一個串流出錯或結束,
pipeline()會自動呼叫.destroy()關閉所有相關串流。 - 支援多個環節:可以輕鬆串接無限多個 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) |
| 適用場景 | 簡單的臨時腳本 | 正式專案、複雜負載環境 |
總結
- 除非是極簡單的單行指令,否則請一律使用
pipeline()。 - 結合
stream/promises與async/await是目前處理串流最穩健的組合。 - 良好的串流管理能確保你的 Node.js 伺服器在長期運行下依然保持極低的資源消耗。