Node.js EventEmitter:事件驅動架構的核心机制

Node.js 本質上是一個「事件驅動」(Event-Driven) 的環境。無論是底層的串流 (Stream)、網路連線還是伺服器請求,幾乎所有的核心模組都源於 EventEmitter。掌握它,你就能理解 Node.js 物件之間如何優雅地進行通訊。

核心概念:觀察者模式 (Observer Pattern)

EventEmitter 實作了經典的觀察者模式,包含兩個角色:

  1. Emitter (發布者):發出特定的事件(例如:檔案上傳完成、使用者登入)。
  2. Listener (監聽者):預先註冊好對應的事件處理函式,當事件發生時立即被觸發。

基礎用法實戰

const EventEmitter = require('events');

// 1. 初始化實例
const myEmitter = new EventEmitter();

// 2. 註冊監聽器
myEmitter.on('order_created', (orderId) => {
  console.log(`[系統通知]: 訂單 ${orderId} 已建立,準備發送確認信...`);
});

// 3. 觸發事件並傳遞數據
myEmitter.emit('order_created', 'ORD-2025001');

常用的進階方法

為了更精細地控制事件,EventEmitter 提供了以下實用工具:

  • once(eventName, listener):監聽器只會被執行 一次,執行後自動銷毀。常用於系統初始化等單次任務。
  • prependListener():將監聽器插入到隊列的首位,確保它比其他已註冊的監聽器更早執行。
  • off()removeListener():移除特定的監聽器,防止在不需要時繼續佔用記憶體。
  • removeAllListeners([eventName]):移除該事件下的所有監聽器。

物件導向整合:繼承 EventEmitter

在開發複雜應用時,我們通常會建立自己的類別並繼承 EventEmitter,讓該物件天然具備發送訊息的能力:

class PaymentGateway extends EventEmitter {
  process(amount) {
    console.log(`處理金額:${amount}`);
    // 模擬非同步金流邏輯...
    this.emit('success', { transactionId: 'TXN123', amount });
  }
}

const gateway = new PaymentGateway();
gateway.on('success', (data) => {
  console.log(`收款成功!交易 ID: ${data.transactionId}`);
});

gateway.process(5000);

極為重要的注意事項

1. 監聽器預設是「同步」執行的

這是一個常見的誤區。雖然 EventEmitter 常用於非同步環境,但 emit 會依序同步地呼叫所有监听器。如果其中一個監聽器包含耗時過長的同步運算,會阻塞後續監聽器的執行。

2. 錯誤處理 (Error Event)

如果一個 EventEmitter 觸發了 'error' 事件,但沒有任何人監聽它,Node.js 會將其視為嚴重錯誤,並 直接導致整個進程崩潰

金律:一律為你的 EventEmitter 加上 .on('error', ...),這是確保系統穩定的基本原則。
myEmitter.on('error', (err) => {
  console.error('捕捉到執行錯誤:', err.message);
});

myEmitter.emit('error', new Error('網路超時'));

3. 最大監聽器限制

預設情況下,一個事件如果註冊超過 10 個 監聽器,Node.js 會發出警告。這通常是為了防止記憶體洩漏。如果有特殊需求,可以使用 setMaxListeners(n) 來調整。

總結

EventEmitter 是 Node.js 異步程式碼組織的基石。它讓你可以將複雜的業務邏輯拆解成多個「解耦」的事件與對應處理器,使代碼更具擴展性。