首頁 >web前端 >js教程 >在Node.js中如何使用EventEmitter處理事件?

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

青灯夜游
青灯夜游轉載
2020-11-02 17:46:092629瀏覽

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

在本教學中我們學習 Node.js 的原生 EvenEmitter 類別。學完後你將了解事件、怎樣使用  EvenEmitter 以及如何在程式中利用事件。另外也會學習 EventEmitter 類別從其他本地模組擴展的內容,並透過一些例子來了解背後的原理。

推薦教學:node js教學

#總之本文涵蓋了關於 EventEmitter 類別的所有內容。

什麼是事件?

當今事件驅動的體系結構非常普遍,事件驅動的程式可以產生、偵測和回應各種事件。

Node.js 的核心部分是事件驅動的,有許多諸如檔案系統(fs)和 stream 這樣的模組本身都是用EventEmitter 寫的。

在事件驅動的程式設計中,事件(event) 是一個或多個動作的結果,這可能是使用者的操作或感測器的定時輸出等。

我們可以把事件驅動程式看作是發布-訂閱模型,其中發布者觸發事件,訂閱者偵聽事件並採取相應的措施。

例如,假設有一個伺服器,使用者可以上傳圖片。在事件驅動的程式設計中,諸如上傳圖片之類的動作將會發出事件,為了利用它,該事件還會有 1 到 n 個訂閱者。

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

這些事件通常是彼此獨立的,儘管它們也可能是相互依賴的。

什麼是EventEmitter?

EventEmitter 類別是 Node.js 的內建類,位於 events 模組。根據文件中的描述:

大部分的Node.js 核心API 都是基於慣用的非同步事件驅動的體系結構所實現的,在該體系結構中,某些類型的物件(稱為“發射器」)發出已命名事件,這些事件會導致呼叫Function 物件(「監聽器」)」

這個類別在某種程度上可以描述為發布-訂閱模型的輔助工具的實現,因為它可以用簡單的方法幫助事件發送器(發布者)發布事件(訊息)給監聽器(訂閱者)。

創建EventEmitters

話雖如此,但還是先創建一個EventEmitter 更實在。可以透過建立類別本身的實例或透過自訂類別實現,然後再建立該類別的實例來完成。

建立 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 秒

EventEmitter 與多個監聽器

下方建立另一個事件傳送器。這是一個計時程序,有三個偵聽器。第一個監聽器每秒更新一次時間,第二個監聽器在計時即將結束時觸發,最後一個在計時結束時觸發:

  • 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

接下来通过扩展 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秒后结束');
});

EventEmitter 的主要函数

eventNames()

此函数将以数组形式返回所有活动的侦听器名称:

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', ...),则新事件也会添加到数组中。

这个方法不会返回已发布的事件,而是返回订阅的事件的列表。

removeListener()

这个函数可以从 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');

在第一个事件触发后,由于 f1f2 都处于活动状态,这两个函数都将被执行。之后从 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);

removeAllListeners()

该函数用于从 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() 会同时触发 f1f2,因为它们当时正处于活动状态。删除它们后,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(&#39;error&#39;, new Error(&#39;countdownTimer 的值不能小于2&#39;));
        }

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

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

处理这个错误的方式与其他事件相同:

myCountDown.on(&#39;error&#39;, (err) => {
    console.error('发生错误:', err);
});

始终对 error 事件进行监听是一种很专业的做法。

使用 EventEmitter 的原生模块

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(&#39;finish&#39;, () => {
  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 写入标准输出管道时,将会触发  stdoutdata 事件。当输出流遇到错误时,将从 stderr 管道发送 data 事件。

最後,在行程退出後,將會觸發 close 事件。

總結

事件驅動的體系結構使我們能夠創建高內聚低耦合的系統。事件表示某個動作的結果,可以定義 1個或多個偵聽器並對其做出反應。

本文深入探討了 EventEmitter 類別及其功能。對其進行實例化後直接使用,並將其行為擴展到了一個自訂物件中。

最後介紹了該類別的一些重要函數。

更多程式設計相關知識,請造訪:程式設計課程! !

以上是在Node.js中如何使用EventEmitter處理事件?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除