JavaScript >> Javascript 文檔 >  >> Node.js

使用 EventEmitter 在 Node.js 中處理事件

簡介

在本教程中,我們將了解 Node 的原生 EventEmitter 班級。您將了解事件,您可以使用 EvenEmitter 做什麼 ,以及如何利用應用程序中的事件。

我們還將介紹從 EventEmitter 擴展而來的其他原生模塊 類和一些示例來了解幕後發生的事情。

簡而言之,我們將涵蓋您需要了解的有關 EventEmitter 的幾乎所有內容 類。

在本教程中,我們將使用一些基本的 ES6 特性,例如 JavaScript 類和箭頭函數。如果您對 ES6 語法有一些先驗知識,這很有幫助,但不是強制性的。

什麼是事件?

整個軟件範式圍繞事件及其使用展開。事件驅動架構現在比較普遍,事件驅動的應用程序會產生、檢測和響應不同類型的事件。

我們可以說 Node.js 的核心部分是事件驅動的,因為許多原生模塊,例如文件系統 (fs ) 和 stream 模塊寫成 EventEmitter 自己。

在事件驅動編程中,事件 是單個或多個動作的結果。例如,這可以是用戶操作或傳感器的周期性輸出。

您可以將事件驅動程序視為發布-訂閱模型,其中發布者觸發事件,訂閱者監聽事件並採取相應行動。

例如,假設我們有一個圖像服務器,用戶可以在其中上傳圖像。在事件驅動編程中,諸如上傳圖像之類的動作會發出一個事件。要使用它,還有 1..n 該事件的訂閱者。

一旦上傳事件被觸發,訂閱者可以通過向網站管理員發送電子郵件來對其做出反應,讓他們知道用戶已經上傳了照片。另一個訂閱者可能會收集有關操作的信息並將它們保存在數據庫中。

這些事件通常相互獨立,但也可能相互依賴。

什麼是 EventEmitter?

EventEmitter class 是一個內置類,位於 events 模塊。根據文檔:

這個類在一定程度上可以被描述為發布/訂閱模型的輔助實現,因為它可以幫助事件發射器 (發布者)發布事件 (消息)和監聽器 (訂閱者)對這些事件採取行動 - 以一種簡單的方式。

創建 EventEmitters

話雖如此,讓我們繼續創建一個 EventEmitter .這可以通過創建類本身的實例來完成,或者通過自定義類實現它然後創建該類的實例。

創建一個EventEmitter 對象

讓我們從一個簡單的事件發射對像開始。我們將創建一個 EventEmitter 它將每秒發出一個事件,其中包含有關應用程序正常運行時間的信息。

首先,導入 EventEmitter events 中的類 模塊:

const { EventEmitter } = require('events');

然後讓我們創建一個 EventEmitter

const timerEventEmitter = new EventEmitter();

從這個對象發布事件很簡單:

timerEventEmitter.emit("update");

我們已指定事件名稱並將其作為事件發布。但是,由於沒有聽眾對此事件做出反應,因此什麼也沒有發生。讓我們讓這個事件每秒重複一次。

使用 setInterval() 方法,創建一個計時器,它將發布 update 每秒事件:

let currentTime = 0;

// This will trigger the update event each passing second
setInterval(() => {
    currentTime++;
    timerEventEmitter.emit('update', currentTime);
}, 1000);

EventEmitter instance 接受一個事件名稱和一組任意參數。在這種情況下,我們通過了 eventName 作為 updatecurrentTime 作為從應用程序開始的時間。

我們通過 emit() 觸發發射器 方法,它使用我們提供的信息推送事件。

準備好事件發射器後,讓我們為它訂閱一個事件監聽器:

timerEventEmitter.on('update', (time) => {
    console.log('Message Received from publisher');
    console.log(`${time} seconds passed since the program started`);
});

使用 on() 方法,傳遞事件名稱來指定我們想將監聽器附加到哪個,允許我們創建監聽器。 開啟 update 事件,運行記錄時間的方法。您可以一遍又一遍地添加相同的偵聽器,每個偵聽器都會訂閱該事件。

on() 的第二個參數 function 是一個回調,它可以接受事件發出的任意數量的額外數據。一旦保持順序,每個偵聽器都可以選擇他們想要的數據。

運行這個腳本應該會產生:

Message Received from publisher
1 seconds passed since the program started
Message Received from publisher
2 seconds passed since the program started
Message Received from publisher
3 seconds passed since the program started
...

相比之下,我們可以使用 once() 訂閱方法 - 如果您只需要在事件第一次觸發時執行某些操作:

timerEventEmitter.once('update', (time) => {
    console.log('Message Received from publisher');
    console.log(`${time} seconds passed since the program started`);
});

運行此代碼將產生:

Message Received from publisher
1 seconds passed since the program started

EventEmitter 具有多個偵聽器

現在,讓我們用三個偵聽器製作一種不同類型的事件發射器。這將是一個倒計時。一個監聽器會每秒更新用戶,一個監聽器會在倒計時接近結束時通知用戶,最後一個監聽器會在倒計時結束時觸發:

  • update - 該事件每秒觸發一次
  • end - 該事件將在倒計時結束時觸發
  • end-soon - 該事件將在倒計時結束前 2 秒觸發

讓我們創建一個函數來創建這個事件發射器並返回它:

const countDown = (countdownTime) => {
    const eventEmitter = new EventEmitter();

    let currentTime = 0;

    // This will trigger the update event each passing second
    const timer = setInterval(() => {
        currentTime++;
        eventEmitter.emit('update', currentTime);

        // Check if countdown has reached to the end
        if (currentTime === countdownTime) {
            clearInterval(timer);
            eventEmitter.emit('end');
        }

        // Check if countdown will end in 2 seconds
        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} seconds since the timer started`);
});

myCountDown.on('end', () => {
    console.log('Countdown is completed');
});

myCountDown.on('end-soon', () => {
    console.log('Count down will end in 2 seconds');
});

此代碼應產生:

1 seconds has been passed since the timer started
2 seconds has been passed since the timer started
3 seconds has been passed since the timer started
Count down will end in 2 seconds
4 seconds has been passed since the timer started
5 seconds has been passed since the timer started
Countdown is completed

擴展EventEmitter

在本節中,讓我們通過擴展 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);
    
            // Check if countdown has reached to the end
            if (this.currentTime === this.countdownTime) {
                clearInterval(timer);
                this.emit('end');
            }
    
            // Check if countdown will end in 2 seconds
            if (this.currentTime === this.countdownTime - 2) {
                this.emit('end-soon');
            }
        }, 1000);
    }
}

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

如您所見,我們可以使用 this.emit() 直接在課堂內。此外,startTimer() 函數用於讓我們控制倒計時開始的時間。否則,它會在對象創建後立即啟動。

讓我們創建一個 CountDown 的新對象 並訂閱它:

const myCountDown = new CountDown(5);

myCountDown.on('update', (t) => {
    console.log(`${t} seconds has been passed since the timer started`);
});

myCountDown.on('end', () => {
    console.log('Countdown is completed');
});

myCountDown.on('end-soon', () => {
    console.log('Count down will be end in 2 seconds');
});

myCountDown.startTimer();

運行這個會導致:

1 seconds has been passed since the timer started
2 seconds has been passed since the timer started
3 seconds has been passed since the timer started
Count down will be end in 2 seconds
4 seconds has been passed since the timer started
5 seconds has been passed since the timer started
Countdown is completed

別名 對於 on() 函數是 addListener() .考慮 end-soon 事件監聽器:

myCountDown.on('end-soon', () => {
    console.log('Count down will be end in 2 seconds');
});

我們可以用 addListener() 做同樣的事情 像這樣:

myCountDown.addListener('end-soon', () => {
    console.log('Count down will be end in 2 seconds');
});

他們都工作。它們幾乎就像同義詞。但是,大多數編碼人員更喜歡使用 on() .

EventEmitter的重要功能

讓我們看看我們可以在 EventEmitter 上使用的一些重要函數 s.

eventNames()

此函數會將所有活動的偵聽器名稱作為數組返回:

const myCountDown = new CountDown(5);

myCountDown.on('update', (t) => {
    console.log(`${t} seconds has been passed since the timer started`);
});

myCountDown.on('end', () => {
    console.log('Countdown is completed');
});

myCountDown.on('end-soon', () => {
    console.log('Count down will be end in 2 seconds');
});

console.log(myCountDown.eventNames());

運行此代碼將導致:

[ 'update', 'end', 'end-soon' ]

如果我們要訂閱另一個事件,例如 myCount.on('some-event', ...) ,新的事件也會被添加到數組中。

請記住,此方法不會返回已發布的事件。它返回一個訂閱它的事件列表。

removeListener()

顧名思義,此函數從 EventEmitter 中刪除訂閱的處理程序 :

const { EventEmitter } = require('events');

const emitter = new EventEmitter();

const f1 = () => {
    console.log('f1 Triggered');
}

const f2 = () => {
    console.log('f2 Triggered');
}

emitter.on('some-event', f1);
emitter.on('some-event', f2);

emitter.emit('some-event');

emitter.removeListener('some-event', f1);

emitter.emit('some-event');

在第一個事件觸發後,由於 f1f2 處於活動狀態 - 這兩個功能都將被執行。之後,我們刪除了 f1 來自 EventEmitter .當我們再次發出事件時,只有 f2 將執行:

f1 Triggered
f2 Triggered
f2 Triggered

別名 對於 removeListener()off() .例如,我們可以這樣寫:

emitter.removeListener('some-event', f1);

如:

emitter.off('some-event', f1);

兩者的效果是一樣的。

removeAllListeners()

同樣,顧名思義 - 此函數將從 EventEmitter 的所有事件中刪除所有偵聽器 :

const { EventEmitter } = require('events');

const emitter = new EventEmitter();

const f1 = () => {
    console.log('f1 Triggered');
}

const f2 = () => {
    console.log('f2 Triggered');
}

emitter.on('some-event', f1);
emitter.on('some-event', f2);

emitter.emit('some-event');

emitter.removeAllListeners();

emitter.emit('some-event');

第一個emit() 將同時觸發 f1f2 因為他們當時很活躍。刪除它們後,emit() 函數會發出事件,但沒有監聽器會響應它:

f1 Triggered
f2 Triggered

錯誤處理

如果你想用 EventEmitter 發出錯誤 ,必須使用 error 事件名稱。這是所有 EventEmitter 的標準 Node.js 中的對象。此活動必須 還附有 Error 目的。例如,可以像這樣發出錯誤事件:

myEventEmitter.emit('error', new Error('Something bad happened'));

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('Value of the countdownTimer cannot be less than 2'));
        }

        this.countdownTime = countdownTime;
        this.currentTime = 0;
    }

    // ...........
}

處理此錯誤的處理方式與其他事件相同:

myCountDown.on('error', (err) => {
    console.error('There was an error:', err);
});

始終擁有 error 的偵聽器被認為是一種很好的做法 事件。

使用 EventEmitter 的原生模塊

Node.js 中的許多原生模塊擴展了 EventEmitter 類,因此它們本身就是事件發射器。

一個很好的例子是 Stream 班級。官方文檔說明:

我們來看看一些經典的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 s 發出一個 finished 完成後的事件。其他事件,例如 error , pipeunpipe 當發生錯誤或讀取流通過管道傳輸到寫入流或從寫入流取消傳輸時發出。

另一個值得注意的類是 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 寫入標準輸出管道 data stdout 事件 (這也是 extends EventEmitter ) 會開火。當輸出流遇到錯誤時,data 事件從 stderr 發送 管道。

最後,進程退出後,close 事件被觸發。

結論

事件驅動架構允許我們創建解耦的系統 但高度凝聚力 .事件代表某個動作的結果,1..n 可以定義監聽器來監聽它們並對它們做出反應。

在本文中,我們深入研究了 EventEmitter 類及其功能。我們已經對其進行了實例化並直接使用它,並將其行為擴展為自定義對象。

最後,我們介紹了該類的一些值得注意的功能。

與往常一樣,源代碼在 GitHub 上可用。


Tutorial JavaScript 教程
  1. 畫布中弧的不同填充樣式顏色

  2. TypeScript 如何改變你的生活

  3. 當使用 key 屬性時,react useState 中的狀態會更新,但需要 useEffect 或類似方法才能更新

  4. 使用 IDE 進行交互式 TypeScript 編程

  5. React、Redux 和 JavaScript 架構

  6. 關於 JavaScript 的 5 個常見誤區

  7. 使用 Nextjs + Next Auth + MySQL + Docker 提升您的本地身份驗證遊戲

  1. 我更喜歡 Reducer 鉤子而不是 State 鉤子,這就是為什麼

  2. 自製的可觀察物

  3. Magento 商店速度優化

  4. 異步編程是否意味著多線程?

  5. React 中的高級列表 - 構建強大的組件(第三部分)

  6. Angular:以聲明方式管理 RxJS 訂閱

  7. 在不阻塞 UI 的情況下迭代數組的最佳方法

  1. 11 個你必須擁有的免費 React 和 Angular 主題和插件

  2. (SHOWCASE) Sveltekit 中的 Netflix Clone(basic)

  3. 如何在 React 中使用 Google 圖表

  4. React Virtual DOM and diffing- algorithm Simplified, Context API