這篇文章將為大家詳細講解有關Node.js中使用EventEmitter處理事件的案例,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
在本教程中我們學習 Node.js 的原生 EvenEmitter 類。學完后你將了解事件、怎樣使用 EvenEmitter 以及如何在程序中利用事件。另外還會學習 EventEmitter 類從其他本地模塊擴展的內容,并通過一些例子了解背后的原理。
總之本文涵蓋了關于 EventEmitter 類的所有內容。
當今事件驅動的體系結構非常普遍,事件驅動的程序可以產生、檢測和響應各種事件。
Node.js 的核心部分是事件驅動的,有許多諸如文件系統(fs)和 stream 這樣的模塊本身都是用 EventEmitter 編寫的。
在事件驅動的編程中,事件(event) 是一個或多個動作的結果,這可能是用戶的操作或者傳感器的定時輸出等。
我們可以把事件驅動程序看作是發布-訂閱模型,其中發布者觸發事件,訂閱者偵聽事件并采取相應的措施。
例如,假設有一個服務器,用戶可以向其上傳圖片。在事件驅動的編程中,諸如上傳圖片之類的動作將會發出一個事件,為了利用它,該事件還會有 1 到 n 個訂閱者。
在觸發上傳事件后,訂閱者可以通過向網站的管理員發電子郵件,讓他們知道用戶已上傳照片并對此做出反應;另一個訂閱者可能會收集有關操作的信息,并將其保存在數據庫中。
這些事件通常是彼此獨立的,盡管它們也可能是相互依賴的。
EventEmitter 類是 Node.js 的內置類,位于 events 模塊。根據文檔中的描述:
大部分的 Node.js 核心 API 都是基于慣用的異步事件驅動的體系結構所實現的,在該體系結構中,某些類型的對象(稱為“發射器”)發出已命名事件,這些事件會導致調用 Function 對象(“監聽器”)”這個類在某種程度上可以描述為發布-訂閱模型的輔助工具的實現,因為它可以用簡單的方法幫助事件發送器(發布者)發布事件(消息)給 監聽器(訂閱者)。
話雖如此,但還是先創建一個 EventEmitter 更加實在??梢酝ㄟ^創建類本身的實例或通過自定義類實現,然后再創建該類的實例來完成。
先從一個簡單的例子開始:創建一個 EventEmitter,它每秒發出一個含有程序運行時間信息的事件。
首先從 events 模塊中導入 EventEmitter 類:
const { EventEmitter } = require('events');然后創建一個 EventEmitter:
const timerEventEmitter = new EventEmitter();
用這個對象發布事件非常容易:
timerEventEmitter.emit("update");前面已經指定了事件名,并把它發布為事件。但是程序沒有任何反應,因為還沒有偵聽器對這個事件做出反應。
先讓這個事件每秒重復一次。用 setInterval() 方法創建一個計時器,每秒發布一次 update 事件:
let currentTime = 0;
// 每秒觸發一次 update 事件
setInterval(() => {
currentTime++;
timerEventEmitter.emit('update', currentTime);
}, 1000);EventEmitter 實例用來接受事件名稱和參數。把 update 作為事件名, currentTime 作為自程序啟動以來的時間進行傳遞。
通過 emit() 方法觸發發射器,該方法用我們提供的信息推送事件。準備好事件發射器之后,為其訂閱事件監聽器:
timerEventEmitter.on('update', (time) => {
console.log('從發布者收到的消息:');
console.log(`程序已經運行了 ${time} 秒`);
});通過 on() 方法創建偵聽器,并傳遞事件名稱來指定希望將偵聽器附加到哪個事件上。 在 update 事件上,運行一個記錄時間的方法。
on() 函數的第二個參數是一個回調,可以接受事件發出的附加數據。
運行代碼將會輸出:
從發布者收到的消息: 程序已經運行了 1 秒 從發布者收到的消息: 程序已經運行了 2 秒 從發布者收到的消息: 程序已經運行了 3 秒 ...
如果只在事件首次觸發時才需要執行某些操作,也可以用 once() 方法進行訂閱:
timerEventEmitter.once('update', (time) => {
console.log('從發布者收到的消息:');
console.log(`程序已經運行了 ${time} 秒`);
});運行這段代碼會輸出:
從發布者收到的消息: 程序已經運行了 1 秒
下面創建另一種事件發送器。這是一個計時程序,有三個偵聽器。第一個監聽器每秒更新一次時間,第二個監聽器在計時即將結束時觸發,最后一個在計時結束時觸發:
update:每秒觸發一次end:在倒數計時結束時觸發end-soon:在計時結束前 2 秒觸發先寫一個創建這個事件發射器的函數:
const countDown = (countdownTime) => {
const eventEmitter = new EventEmitter();
let currentTime = 0;
// 每秒觸發一次 update 事件
const timer = setInterval(() => {
currentTime++;
eventEmitter.emit('update', currentTime);
// 檢查計時是否已經結束
if (currentTime === countdownTime) {
clearInterval(timer);
eventEmitter.emit('end');
}
// 檢查計時是否會在 2 秒后結束
if (currentTime === countdownTime - 2) {
eventEmitter.emit('end-soon');
}
}, 1000);
return eventEmitter;
};這個函數啟動了一個每秒鐘發出一次 update 事件的事件。
第一個 if 用來檢查計時是否已經結束并停止基于間隔的事件。如果已結束將會發布 end 事件。
如果計時沒有結束,那么就檢查計時是不是離結束還有 2 秒,如果是則發布 end-soon 事件。
向該事件發射器添加一些訂閱者:
const myCountDown = countDown(5);
myCountDown.on('update', (t) => {
console.log(`程序已經運行了 ${t} 秒`);
});
myCountDown.on('end', () => {
console.log('計時結束');
});
myCountDown.on('end-soon', () => {
console.log('計時將在2秒后結束');
});這段代碼將會輸出:
程序已經運行了 1 秒 程序已經運行了 2 秒 程序已經運行了 3 秒 計時將在2秒后結束 程序已經運行了 4 秒 程序已經運行了 5 秒 計時結束
接下來通過擴展 EventEmitter 類來實現相同的功能。首先創建一個處理事件的 CountDown 類:
const { EventEmitter } = require('events');
class CountDown extends EventEmitter {
constructor(countdownTime) {
super();
this.countdownTime = countdownTime;
this.currentTime = 0;
}
startTimer() {
const timer = setInterval(() => {
this.currentTime++;
this.emit('update', this.currentTime);
// 檢查計時是否已經結束
if (this.currentTime === this.countdownTime) {
clearInterval(timer);
this.emit('end');
}
// 檢查計時是否會在 2 秒后結束
if (this.currentTime === this.countdownTime - 2) {
this.emit('end-soon');
}
}, 1000);
}
}可以在類的內部直接使用 this.emit()。另外 startTimer() 函數用于控制計時開始的時間。否則它將在創建對象后立即開始計時。
創建一個 CountDown 的新對象并訂閱它:
const myCountDown = new CountDown(5);
myCountDown.on('update', (t) => {
console.log(`計時開始了 ${t} 秒`);
});
myCountDown.on('end', () => {
console.log('計時結束');
});
myCountDown.on('end-soon', () => {
console.log('計時將在2秒后結束');
});
myCountDown.startTimer();運行程序會輸出:
程序已經運行了 1 秒 程序已經運行了 2 秒 程序已經運行了 3 秒 計時將在2秒后結束 程序已經運行了 4 秒 程序已經運行了 5 秒 計時結束
on() 函數的別名是 addListener()??匆幌?end-soon 事件監聽器:
myCountDown.on('end-soon', () => {
console.log('計時將在2秒后結束');
});也可以用 addListener() 來完成相同的操作,例如:
myCountDown.addListener('end-soon', () => {
console.log('計時將在2秒后結束');
});此函數將以數組形式返回所有活動的偵聽器名稱:
const myCountDown = new CountDown(5);
myCountDown.on('update', (t) => {
console.log(`程序已經運行了 ${t} 秒`);
});
myCountDown.on('end', () => {
console.log('計時結束');
});
myCountDown.on('end-soon', () => {
console.log('計時將在2秒后結束');
});
console.log(myCountDown.eventNames());運行這段代碼會輸出:
[ 'update', 'end', 'end-soon' ]
如果要訂閱另一個事件,例如 myCount.on('some-event', ...),則新事件也會添加到數組中。
這個方法不會返回已發布的事件,而是返回訂閱的事件的列表。
這個函數可以從 EventEmitter 中刪除已訂閱的監聽器:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const f1 = () => {
console.log('f1 被觸發');
}
const f2 = () => {
console.log('f2 被觸發');
}
emitter.on('some-event', f1);
emitter.on('some-event', f2);
emitter.emit('some-event');
emitter.removeListener('some-event', f1);
emitter.emit('some-event');在第一個事件觸發后,由于 f1 和 f2 都處于活動狀態,這兩個函數都將被執行。之后從 EventEmitter 中刪除了 f1。當再次發出事件時,將會只執行 f2:
f1 被觸發 f2 被觸發 f2 被觸發
An alias for removeListener() is off(). For example, we could have written:
removeListener() 的別名是 off()。例如可以這樣寫:
emitter.off('some-event', f1);該函數用于從 EventEmitter 的所有事件中刪除所有偵聽器:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const f1 = () => {
console.log('f1 被觸發');
}
const f2 = () => {
console.log('f2 被觸發');
}
emitter.on('some-event', f1);
emitter.on('some-event', f2);
emitter.emit('some-event');
emitter.removeAllListeners();
emitter.emit('some-event');第一個 emit() 會同時觸發 f1 和 f2,因為它們當時正處于活動狀態。刪除它們后,emit() 函數將發出事件,但沒有偵聽器對此作出響應:
f1 被觸發 f2 被觸發
如果要在 EventEmitter 發出錯誤,必須用 error 事件名來完成。這是 Node.js 中所有 EventEmitter 對象的標準配置。這個事件必須還要有一個 Error 對象。例如可以像這樣發出錯誤事件:
myEventEmitter.emit('error', new Error('出現了一些錯誤'));error 事件的偵聽器都應該有一個帶有一個參數的回調,用來捕獲 Error 對象并處理。如果 EventEmitter 發出了 error 事件,但是沒有訂閱者訂閱 error 事件,那么 Node.js 程序將會拋出這個 Error。這會導致 Node.js 進程停止運行并退出程序,同時在控制臺中顯示這個錯誤的跟蹤棧。
例如在 CountDown 類中,countdownTime參數的值不能小于 2,否則會無法觸發 end-soon 事件。在這種情況下應該發出一個 error 事件:
class CountDown extends EventEmitter {
constructor(countdownTime) {
super();
if (countdownTimer < 2) {
this.emit('error', new Error('countdownTimer 的值不能小于2'));
}
this.countdownTime = countdownTime;
this.currentTime = 0;
}
// ...........
}處理這個錯誤的方式與其他事件相同:
myCountDown.on('error', (err) => {
console.error('發生錯誤:', err);
});始終對 error 事件進行監聽是一種很專業的做法。
Node.js 中許多原生模塊擴展了EventEmitter 類,因此它們本身就是事件發射器。
一個典型的例子是 Stream 類。官方文檔指出:
流可以是可讀的、可寫的,或兩者均可。所有流都是 EventEmitter 的實例。先看一下經典的 Stream 用法:
const fs = require('fs');
const writer = fs.createWriteStream('example.txt');
for (let i = 0; i < 100; i++) {
writer.write(`hello, #${i}!\n`);
}
writer.on('finish', () => {
console.log('All writes are now complete.');
});
writer.end('This is the end\n');但是,在寫操作和 writer.end() 調用之間,我們添加了一個偵聽器。 Stream 在完成后會發出一個 finished 事件。在發生錯誤時會發出 error 事件,把讀取流通過管道傳輸到寫入流時會發出 pipe 事件,從寫入流中取消管道傳輸時,會發出 unpipe 事件。
另一個類是 child_process 類及其 spawn() 方法:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});當 child_process 寫入標準輸出管道時,將會觸發 stdout 的 data 事件。當輸出流遇到錯誤時,將從 stderr 管道發送 data 事件。
最后,在進程退出后,將會觸發 close 事件。
關于Node.js中使用EventEmitter處理事件的案例就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。