server.listen() 或 Day 5 的 readStream.on('end', ...),這些能在「某個特定時間點」自動觸發函式執行的機制,背後全都是靠 Node.js 的內建核心模組:events 裡的 EventEmitter 在撐腰。傳統的程式碼是「指令式」的:你呼叫函式 A,接著呼叫函式 B,一步一步往下走。
事件驅動架構則是「訂閱與發佈 (Pub/Sub)」模式,就像一個廣播電台:
這讓程式碼的模組之間達到了極致的「低耦合」,發佈者不需要知道是誰在監聽,它只管大喊一聲「任務完成啦!」就好。
我們來模擬一個真實後端場景:當使用者「註冊成功」時,系統需要同時做三件事:寫入日誌、發送歡迎 Email、發送發票。如果用傳統寫法,註冊函式會變得無比肥大;但用 EventEmitter,程式碼會變得異常優雅。
在 hello-node 資料夾下,新增一個 event-demo.js,輸入以下程式碼:
import { EventEmitter } from 'events';
import dayjs from 'dayjs'; // 沿用 Day 2 安裝的套件
// 建立一個繼承自 EventEmitter 的自定義類別 (實務上常用的做法)
class UserSystem extends EventEmitter {
registerUser(username, email) {
console.log(`\n[系統] 正在處理 ${username} 的註冊程序...`);
// 模擬寫入資料庫的時間
setTimeout(() => {
console.log(`[系統] ${username} 註冊成功!`);
// 關鍵:註冊完成後,對全域廣播一個名為 'userRegistered' 的事件
// 並將使用者的資料當作「包裹」一起廣播出去
this.emit('userRegistered', { username, email, time: dayjs().format('HH:mm:ss') });
}, 1000);
}
}
// 實例化我們的系統
const myApp = new UserSystem();
// ==========================================
// 訂閱者 1:負責寫入安全日誌
myApp.on('userRegistered', (userData) => {
console.log(` [日誌系統] 記錄:${userData.username} 於 ${userData.time} 註冊。`);
});
// 訂閱者 2:負責發送 Email
myApp.on('userRegistered', (userData) => {
console.log(` [信件系統] 正在發送歡迎信至:${userData.email}`);
});
// 訂閱者 3:負責發放新手禮包 (使用 .once 只會觸發一次,就算重複註冊也不會重複發放)
myApp.once('userRegistered', (userData) => {
console.log(` [獎勵系統] 已發放新手大禮包給:${userData.username}`);
});
// ==========================================
console.log(" 伺服器啟動,等待使用者註冊...");
// 模擬兩次使用者註冊
myApp.registerUser("Bibo", "bibo@example.com");
setTimeout(() => {
myApp.registerUser("Bibo_分身", "bibo2@example.com");
}, 3000);
在終端機執行 node event-demo.js。
你會觀察到:
.once(),所以沒有再次觸發。