您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。