在Node.js中使用事件发射器

来自菜鸟教程
跳转至:导航、​搜索

作者选择了 COVID-19 Relief Fund 作为 Write for DOnations 计划的一部分来接受捐赠。

介绍

事件发射器Node.js 中的对象,它通过发送消息来指示操作已完成来触发事件。 JavaScript 开发人员可以编写代码来监听来自事件发射器的 events,允许他们在每次触发这些事件时执行 functions。 在这种情况下,事件由标识 string 和需要传递给侦听器的任何数据组成。

通常在 Node.js 中,当我们希望在完成另一个动作时发生一个动作,我们使用 异步编程 技术,如嵌套回调或链接承诺。 然而,这些技术将触发动作和产生的动作紧密结合在一起,使得将来很难修改产生的动作。 事件发射器提供了一种不同的方式来构建这种关系:发布-订阅模式。 在这种软件架构模式中,发布者(事件发射器)发送消息(事件),订阅者接收事件并执行操作。 这种模式的强大之处在于发布者不需要知道订阅者。 发布者发布消息,并由订阅者以各自的方式对其做出反应。 如果我们想改变应用程序的行为,我们可以调整订阅者对事件的反应方式,而无需更改发布者。

在本文中,我们将为 TicketManager JavaScript 类 创建一个事件监听器,允许用户购买门票。 我们将为 buy 事件设置监听器,该事件将在每次购票时触发。 这个过程还将展示如何管理来自发射器的错误事件以及如何管理事件订阅者。

先决条件

第 1 步 - 发出事件

在这一步中,我们将探索在 Node.js 中创建事件发射器的两种最常见的方法。 第一种是直接使用事件发射器对象,第二种是创建一个扩展事件发射器对象的对象。

决定使用哪一个取决于您的事件与对象操作的耦合程度。 如果您要发出的事件是对象操作的效果,您可能会从事件发射器对象扩展以访问其功能以方便使用。 如果您要发出的事件独立于您的业务对象或者是来自许多业务对象的操作的结果,那么您将改为创建一个由您的对象引用的独立事件发射器对象。

让我们从创建一个独立的事件发射对象开始。 我们将首先创建一个文件夹来存储我们所有的代码。 在您的终端中,创建一个名为 event-emitters 的新文件夹:

mkdir event-emitters

然后进入那个文件夹:

cd event-emitters

在文本编辑器中打开第一个事件发射器 firstEventEmitter.js。 我们将使用 nano,因为它在终端中可用:

nano firstEventEmitter.js

在 Node.js 中,我们通过 EventEmitter 类发出事件。 此类是 events 模块的一部分。 让我们首先通过添加以下行在我们的文件中加载 events 模块:

事件发射器/firstEventEmitter.js

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

导入类后,我们可以使用它从中创建一个新的对象实例:

事件发射器/firstEventEmitter.js

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

const firstEmitter = new EventEmitter();

让我们通过在 firstEventEmitter.js 末尾添加以下突出显示的行来发出事件:

事件发射器/firstEventEmitter.js

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

const firstEmitter = new EventEmitter();


firstEmitter.emit("My first event");

emit() 函数用于触发事件。 我们需要将事件的名称作为字符串传递给它。 我们可以在事件名称之后添加任意数量的参数。 只有名称的事件相当有限; 其他参数允许我们向我们的听众发送数据。 当我们设置票务管理器时,我们的事件将在发生购买时传递有关购买的数据。 请记住事件的名称,因为事件侦听器将通过此名称来识别它。

注意: 虽然我们在本例中没有捕获它,但如果有事件的侦听器,emit() 函数将返回 true。 如果事件没有侦听器,则返回 false


让我们运行这个文件看看会发生什么。 保存退出nano,然后用node命令执行文件:

node firstEventEmitter.js

脚本完成执行后,您将在终端中看不到任何输出。 那是因为我们没有在 firstEventEmitter.js 中记录任何消息,并且没有任何东西可以监听发送的事件。 发出事件,但对这些事件没有任何作用。

让我们努力看一个更完整的发布、收听和对事件采取行动的示例。 我们将通过创建票务管理器示例应用程序来做到这一点。 票务管理器将公开一个购买门票的功能。 购买门票后,将发送包含购买者详细信息的事件。 稍后,我们将创建另一个 Node.js 模块来模拟向购买者的电子邮件发送确认购买的电子邮件。

让我们从创建票务管理器开始。 它将扩展 EventEmitter 类,这样我们就不必创建单独的事件发射器对象来发射事件。

在同一工作目录中,创建并打开一个名为 ticketManager.js 的新文件:

nano ticketManager.js

与第一个事件发射器一样,我们需要从 events 模块导入 EventEmitter 类。 将以下代码放在文件的开头:

事件发射器/ticketManager.js

const EventEmitter = require("events");

现在,创建一个新的 TicketManager 类,它将很快定义购票方法:

事件发射器/ticketManager.js

const EventEmitter = require("events");

class TicketManager extends EventEmitter {}

在这种情况下,TicketManager 类扩展了 EventEmitter 类。 这意味着 TicketManager 类继承了 EventEmitter 类的方法和属性。 这就是它访问 emit() 方法的方式。

在我们的票务管理器中,我们希望提供可以购买的票的初始供应。 我们将通过在我们的 constructor() 中接受初始供应来做到这一点,这是一个在创建类的新对象时调用的特殊函数。 将以下构造函数添加到 TicketManager 类:

事件发射器/ticketManager.js

const EventEmitter = require("events");

class TicketManager extends EventEmitter {
    constructor(supply) {
        super();
        this.supply = supply;
    }
}

构造函数有一个 supply 参数。 这是一个详细说明我们可以出售的门票的初始供应量的数字。 即使我们声明 TicketManagerEventEmitter 的子类,我们仍然需要调用 super() 来访问 EventEmitter 的方法和属性。 super() 函数调用父函数的构造函数,在本例中为 EventEmitter

最后,我们用 this.supply 为对象创建一个 supply 属性,并给它构造函数传入的值。

现在,让我们添加一个 buy() 方法,该方法将在购票时调用。 此方法会将供应量减少 1 并发出带有购买数据的事件。

添加buy()方法如下:

事件发射器/ticketManager.js

const EventEmitter = require("events");

class TicketManager extends EventEmitter {
    constructor(supply) {
        super();
        this.supply = supply;
    }
    
    buy(email, price) {
        this.supply--;
        this.emit("buy", email, price, Date.now());
    }
}

buy() 函数中,我们获取购买者的电子邮件地址和他们为机票支付的价格。 然后我们将门票的供应量减少一张。 我们以发出 buy 事件结束。 这一次,我们发出一个带有额外数据的事件:函数中传递的电子邮件和价格,以及购买时间的时间戳。

为了让我们的其他 Node.js 模块 可以使用这个类,我们需要导出它。 在文件末尾添加这一行:

事件发射器/ticketManager.js

...

module.exports = TicketManager

保存并退出文件。

我们已经完成了事件发射器 TicketManager 的设置。 现在我们已经为推送事件做好了准备,我们可以继续阅读和处理这些事件。 为此,我们将在下一步中创建事件侦听器。

第 2 步 — 监听事件

Node.js 允许我们使用事件发射器对象的 on() 函数为事件添加监听器。 这会侦听特定的事件名称并在触发事件时触发回调。 添加侦听器通常如下所示:

eventEmitter.on(event_name, callback_function) {
    action
}

注意::Node.js 将 on() 方法别名为 addListener()。 他们执行相同的任务。 在本教程中,我们将继续使用 on()


让我们通过聆听我们的第一个活动来获得一些第一手经验。 创建一个名为 firstListener.js 的新文件:

nano firstListener.js

作为 on() 函数如何工作的演示,让我们在接收到第一个事件时记录一条简单的消息。

首先,让我们将 TicketManager 导入到我们的新 Node.js 模块中。 将以下代码添加到 firstListener.js 中:

事件发射器/firstListener.js

const TicketManager = require("./ticketManager");

const ticketManager = new TicketManager(10);

回想一下 TicketManager 对象在创建时需要它们的初始票证。 这就是我们传递 10 参数的原因。

现在让我们添加我们的第一个 Node.js 事件监听器。 它将监听 buy 事件。 添加以下突出显示的代码:

事件发射器/firstListener.js

const TicketManager = require("./ticketManager");

const ticketManager = new TicketManager(10);

ticketManager.on("buy", () => {
    console.log("Someone bought a ticket!");
});

为了添加一个新的监听器,我们使用了 on() 函数,它是 ticketManager 对象的一部分。 on() 方法适用于所有事件发射器对象,并且由于 TicketManager 继承自 EventEmitter 类,因此该方法适用于所有 TicketManager 实例对象。

on() 方法的第二个参数是回调函数,写成 箭头函数 。 此函数中的代码在事件发出后运行。 在这种情况下,如果发出 buy 事件,我们会将 "Someone bought a ticket!" 记录到控制台。

现在我们设置了一个监听器,让我们使用 buy() 函数,以便发出事件。 在文件末尾添加以下内容:

事件发射器/firstListener.js

...

ticketManager.buy("test@email.com", 20);

这将使用 test@email.com 的用户电子邮件和 20 的票价执行 buy 方法。

保存并退出文件。

现在用 node 运行这个脚本:

node firstListener.js

您的控制台将显示以下内容:

OutputSomeone bought a ticket!

你的第一个事件监听器工作了。 让我们看看如果我们购买多张票会发生什么。 在文本编辑器中重新打开 firstListener.js

nano firstListener.js

在文件末尾,再次调用 buy() 函数:

事件发射器/firstListener.js

...

ticketManager.buy("test@email.com", 20);
ticketManager.buy("test@email.com", 20);

保存并退出文件。 让我们用 Node.js 运行脚本,看看会发生什么:

node firstListener.js

您的控制台将显示以下内容:

OutputSomeone bought a ticket!
Someone bought a ticket!

由于 buy() 函数被调用了两次,因此发出了两个 buy 事件。 我们的听众两个都接了。

有时我们只对第一次触发事件感兴趣,而不是在它发出的所有时间。 对于这种情况,Node.js 使用 once() 函数提供了 on() 的替代方案。

on() 类似,once() 函数接受事件名称作为其第一个参数,并接受一个在事件触发时调用的回调函数作为其第二个参数。 在后台,当使用 once() 的侦听器发出和接收事件时,Node.js 会自动删除侦听器,然后执行回调函数中的代码。

让我们通过编辑 firstListener.js 来看看 once() 的作用:

nano firstListener.js

在文件末尾,使用 once() 添加一个新的事件侦听器,如以下突出显示的行:

事件发射器/firstListener.js

const TicketManager = require("./ticketManager");

const ticketManager = new TicketManager(10);

ticketManager.on("buy", () => {
        console.log("Someone bought a ticket!");
});

ticketManager.buy("test@email.com", 20);
ticketManager.buy("test@email.com", 20);

ticketManager.once("buy", () => {
    console.log("This is only called once");
});

保存并退出文件并使用 node 运行此程序:

node firstListener.js

输出和上次一样:

OutputSomeone bought a ticket!
Someone bought a ticket!

虽然我们使用 once() 添加了一个新的事件侦听器,但它是在发出 buy 事件之后添加的。 因此,侦听器没有检测到这两个事件。 你不能监听过去已经发生的事件。 添加侦听器时,您只能捕获之后发生的事件。

让我们再添加几个 buy() 函数调用,以便我们可以确认 once() 侦听器只响应一次。 像以前一样在文本编辑器中打开 firstListener.js

nano firstListener.js

在文件末尾添加以下调用:

事件发射器/firstListener.js

...

ticketManager.once("buy", () => {
    console.log("This is only called once");
});

ticketManager.buy("test@email.com", 20);
ticketManager.buy("test@email.com", 20);

保存退出,然后执行这个程序:

node firstListener.js

您的输出将是:

OutputSomeone bought a ticket!
Someone bought a ticket!
Someone bought a ticket!
This is only called once
Someone bought a ticket!

前两行来自添加 once() 侦听器之前的前两个 buy() 调用。 添加新的事件侦听器不会删除以前的事件侦听器,因此我们添加的第一个事件侦听器仍然处于活动状态并记录消息。

由于带有 on() 的事件侦听器是在带有 once() 的事件侦听器之前声明的,因此我们在 This is only called once 之前看到了 Someone bought a ticket!。 这两行都响应倒数第二个 buy 事件。

最后,当最后一次调用 buy() 时,事件发射器只有第一个使用 on() 创建的侦听器。 如前所述,当使用 once() 创建的事件监听器收到事件时,会自动将其移除。

现在我们已经添加了事件侦听器来检测我们的发射器,我们将了解如何使用这些侦听器捕获数据。

第 3 步 — 捕获事件数据

到目前为止,您已经设置了事件侦听器来对发出的事件做出反应。 发出的事件也传递数据。 让我们看看如何捕获伴随事件的数据。

我们将从创建一些新的 Node.js 模块开始:电子邮件服务和数据库服务。 它们将分别用于模拟发送电子邮件和保存到数据库。 然后,我们将把它们与我们的主要 Node.js 脚本——index.js 绑定在一起。

让我们从编辑我们的电子邮件服务模块开始。 在文本编辑器中打开文件:

nano emailService.js

我们的电子邮件服务由一个类组成,该类包含一个方法——send()。 此方法需要与 buy 事件一起发出的电子邮件。 将以下代码添加到您的文件中:

事件发射器/emailService.js

class EmailService {
    send(email) {
        console.log(`Sending email to ${email}`);
    }
}

module.exports = EmailService

此代码创建一个包含 send() 函数的 EmailService 类。 代替发送实际电子邮件,它使用 模板文字 将消息记录到控制台,其中包含购买机票的人的电子邮件地址。 在继续之前保存并退出。

让我们设置数据库服务。 使用文本编辑器打开 databaseService.js

nano databaseService.js

数据库服务通过其 save() 方法将我们的购买数据保存到数据库中。 将以下代码添加到 databaseService.js

事件发射器/databaseService.js

class DatabaseService {
    save(email, price, timestamp) {
        console.log(`Running query: INSERT INTO orders VALUES (email, price, created) VALUES (${email}, ${price}, ${timestamp})`);
    }
}

module.exports = DatabaseService

这将创建一个新的 DatabaseService 类,其中包含一个 save() 方法。 与电子邮件服务的 send() 方法类似,save() 函数使用伴随 buy 事件的数据,将其记录到控制台而不是实际将其插入数据库。 此方法需要购买者的电子邮件、门票价格和购买门票的时间才能运行。 保存并退出文件。

我们将使用最后一个文件将 TicketManagerEmailServiceDatabaseService 放在一起。 它将为 buy 事件设置监听器,并调用电子邮件服务的 send() 函数和数据库服务的 save() 函数。

在文本编辑器中打开 index.js 文件:

nano index.js

首先要做的是导入我们正在使用的模块:

事件发射器/index.js

const TicketManager = require("./ticketManager");
const EmailService = require("./emailService");
const DatabaseService = require("./databaseService");

接下来,让我们为我们导入的类创建对象。 我们将为此演示设置三张的低票供应量:

事件发射器/index.js

const TicketManager = require("./ticketManager");
const EmailService = require("./emailService");
const DatabaseService = require("./databaseService");

const ticketManager = new TicketManager(3);
const emailService = new EmailService();
const databaseService = new DatabaseService();

我们现在可以使用实例化对象设置我们的侦听器。 每当有人买票时,我们都想给他们发送一封电子邮件,并将数据保存到数据库中。 将以下侦听器添加到您的代码中:

事件发射器/index.js

const TicketManager = require("./ticketManager");
const EmailService = require("./emailService");
const DatabaseService = require("./databaseService");

const ticketManager = new TicketManager(3);
const emailService = new EmailService();
const databaseService = new DatabaseService();


ticketManager.on("buy", (email, price, timestamp) => {
    emailService.send(email);
    databaseService.save(email, price, timestamp);
});

和之前一样,我们使用 on() 方法添加一个监听器。 这次的不同之处在于我们的回调函数中有三个参数。 每个参数对应于事件发出的数据。 提醒一下,这是 buy() 函数中的发射器代码:

事件发射器/ticketManager.js

this.emit("buy", email, price, Date.now());

在我们的回调中,我们首先从发射器捕获 email,然后是 price,最后是 Date.now() 数据,我们将其捕获为 timestamp

当我们的监听器检测到 buy 事件时,它会从 emailService 对象调用 send() 函数以及从 databaseService 对象调用 save() 函数]。 为了测试这个设置是否有效,让我们调用文件末尾的 buy() 函数:

事件发射器/index.js

...

ticketManager.buy("test@email.com", 10);

保存并退出编辑器。 现在让我们用 node 运行这个脚本并观察接下来会发生什么。 在您的终端中输入:

node index.js

您将看到以下输出:

OutputSending email to test@email.com
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588720081832)

数据被成功捕获并在我们的回调函数中返回。 有了这些知识,您可以为具有不同事件名称和数据的各种发射器设置侦听器。 但是,使用事件发射器处理错误事件有一些细微差别。

接下来,让我们看看如何处理错误事件以及我们应该遵循哪些标准。

第 4 步 — 处理错误事件

如果事件发射器不能执行它的动作,它应该发出一个事件来表示动作失败。 在 Node.js 中,事件发射器发出失败信号的标准方法是发射 错误事件

错误事件的名称必须设置为 error。 它还必须伴随一个 Error 对象。 让我们看一个发出错误事件的实际示例。

每次调用 buy() 函数时,我们的工单管理器都会将供应量减少一。 现在没有什么能阻止它卖出比现有更多的门票。 让我们修改 buy() 函数,如果票的供应量达到 0 并且有人想买票,我们会发出一个错误,指示我们缺货。

再次在文本编辑器中打开 ticketManager.js

nano ticketManager.js

现在编辑buy()函数如下:

事件发射器/ticketManager.js

...

buy(email, price) {
    if (this.supply > 0) {
        this.supply—;
        this.emit("buy", email, price, Date.now());
        return;
    }

    this.emit("error", new Error("There are no more tickets left to purchase"));
}
...

我们添加了一个 if 语句,如果我们的供应量大于零,则允许购买门票。 如果我们没有任何其他票证,我们将发出 error 事件。 error 事件与一个新的 Error 对象一起发出,该对象包含我们抛出此错误的原因的描述。

保存并退出文件。 让我们尝试在我们的 index.js 文件中抛出这个错误。 现在,我们只买一张票。 我们用三张票实例化了 ticketManager 对象,所以如果我们尝试购买四张票,我们应该会得到一个错误。

使用文本编辑器编辑 index.js

nano index.js

现在在文件末尾添加以下行,这样我们总共可以购买四张票:

事件发射器/index.js

...

ticketManager.buy("test@email.com", 10);
ticketManager.buy("test@email.com", 10);
ticketManager.buy("test@email.com", 10);
ticketManager.buy("test@email.com", 10);

保存并退出编辑器。

让我们执行这个文件看看会发生什么:

node index.js

您的输出将是:

OutputSending email to test@email.com
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588724932796)
Sending email to test@email.com
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588724932812)
Sending email to test@email.com
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588724932812)
events.js:196
      throw er; // Unhandled 'error' event
      ^

Error: There are no more tickets left to purchase
    at TicketManager.buy (/home/sammy/event-emitters/ticketManager.js:16:28)
    at Object.<anonymous> (/home/sammy/event-emitters/index.js:17:15)
    at Module._compile (internal/modules/cjs/loader.js:1128:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
    at Module.load (internal/modules/cjs/loader.js:983:32)
    at Function.Module._load (internal/modules/cjs/loader.js:891:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47
Emitted 'error' event on TicketManager instance at:
    at TicketManager.buy (/home/sammy/event-emitters/ticketManager.js:16:14)
    at Object.<anonymous> (/home/sammy/event-emitters/index.js:17:15)
    [... lines matching original stack trace ...]
    at internal/main/run_main_module.js:17:47

前三个 buy 事件被正确处理,但在第四个 buy 事件中我们的程序崩溃了。 让我们检查错误消息的开头:

Output...
events.js:196
      throw er; // Unhandled 'error' event
      ^

Error: There are no more tickets left to purchase
    at TicketManager.buy (/home/sammy/event-emitters/ticketManager.js:16:28)
...

前两行突出显示引发了错误。 评论说 "Unhandled 'error' event"。 如果事件发射器发出错误并且我们没有为错误事件附加侦听器,Node.js 会抛出错误并使程序崩溃。

如果您正在侦听事件发射器,则始终侦听 error 事件被认为是最佳实践。 如果您没有为错误设置侦听器,那么如果发出一个错误侦听器,您的整个应用程序将会崩溃。 使用错误侦听器,您可以优雅地处理它。

为了遵循最佳实践,让我们设置一个错误侦听器。 重新打开index.js文件:

nano index.js

在我们开始购票之前添加一个监听器。 请记住,侦听器只能响应添加后发出的事件。 像这样插入一个错误监听器:

事件发射器/index.js

...

ticketManager.on("error", (error) => {
    console.error(`Gracefully handling our error: ${error}`);
});

ticketManager.buy("test@email.com", 10);
ticketManager.buy("test@email.com", 10);
ticketManager.buy("test@email.com", 10);
ticketManager.buy("test@email.com", 10);

当我们收到错误事件时,我们将使用 console.error() 将其记录到控制台。

保存并离开 nano。 重新运行脚本以查看我们的错误事件是否正确处理:

node index.js

这次将显示以下输出:

OutputSending email to test@email.com
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588726293332)
Sending email to test@email.com
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588726293348)
Sending email to test@email.com
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588726293348)
Gracefully handling our error: Error: There are no more tickets left to purchase

从最后一行,我们确认我们的错误事件正在由我们的第二个侦听器处理,并且 Node.js 进程没有崩溃。

现在我们已经介绍了发送和监听事件的概念,让我们看看一些可用于管理事件监听器的附加功能。

第 5 步 — 管理事件监听器

事件发射器带有一些机制来监视和控制订阅事件的侦听器数量。 要了解有多少侦听器正在处理一个事件,我们可以使用每个对象中包含的 listenerCount() 方法。

listenerCount() 方法接受一个参数:您想要计数的事件名称。 让我们在 index.js 中查看它的实际效果。

使用 nano 或您选择的文本编辑器打开文件:

nano index.js

您当前调用了 buy() 函数四次。 删除这四行。 当你这样做时,添加这两个新行,使你的整个 index.js 看起来像这样:

事件发射器/index.js

const TicketManager = require("./ticketManager");
const EmailService = require("./emailService");
const DatabaseService = require("./databaseService");

const ticketManager = new TicketManager(3);
const emailService = new EmailService();
const databaseService = new DatabaseService();

ticketManager.on("buy", (email, price, timestamp) => {
    emailService.send(email);
    databaseService.save(email, price, timestamp);
});

ticketManager.on("error", (error) => {
    console.error(`Gracefully handling our error: ${error}`);
});

console.log(`We have ${ticketManager.listenerCount("buy")} listener(s) for the buy event`);
console.log(`We have ${ticketManager.listenerCount("error")} listener(s) for the error event`);

我们已经从上一节中删除了对 buy() 的调用,而是在控制台中记录了两行。 第一条日志语句使用 listenerCount() 函数显示 buy() 事件的侦听器数量。 第二条日志语句显示了我们有多少个 error 事件的侦听器。

保存并退出。 现在使用 node 命令运行您的脚本:

node index.js

你会得到这个输出:

OutputWe have 1 listener(s) for the buy event
We have 1 listener(s) for the error event

我们只对 buy 事件和 error 事件使用了一次 on() 函数,所以这个输出符合我们的预期。

接下来,我们将使用 listenerCount() 从事件发射器中移除侦听器。 当事件的周期不再适用时,我们可能希望删除事件侦听器。 例如,如果我们的票务管理器正在用于特定音乐会,当音乐会结束时,您将删除事件侦听器。

在 Node.js 中,我们使用 off() 函数从事件发射器中移除事件监听器。 off() 方法接受两个参数:事件名称和监听它的函数。

注意:与on()函数类似,Node.js将off()方法别名为removeListener()。 他们都做同样的事情,有同样的论据。 在本教程中,我们将继续使用 off()


对于 off() 函数的第二个参数,我们需要一个对正在监听事件的回调的引用。 因此,要删除一个事件监听器,它的回调必须保存到某个变量或常量中。 就目前而言,我们无法使用 off() 函数删除 buyerror 的当前事件侦听器。

要查看 off() 的实际效果,让我们添加一个新的事件侦听器,我们将在后续调用中删除它。 首先,让我们在一个变量中定义回调,以便我们稍后可以在 off() 中引用它。 用 nano 打开 index.js

nano index.js

index.js 末尾添加:

事件发射器/index.js

...

const onBuy = () => {
    console.log("I will be removed soon");
};

现在为 buy 事件添加另一个事件监听器:

事件发射器/index.js

...

ticketManager.on("buy", onBuy);

为了确保我们成功添加了事件监听器,让我们打印 buy 的监听器计数并调用 buy() 函数。

事件发射器/index.js

...

console.log(`We added a new event listener bringing our total count for the buy event to: ${ticketManager.listenerCount("buy")}`);
ticketManager.buy("test@email", 20);

保存并退出文件,然后运行程序:

node index.js

终端中将显示以下消息:

OutputWe have 1 listener(s) for the buy event
We have 1 listener(s) for the error event
We added a new event listener bringing our total count for the buy event to: 2
Sending email to test@email
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588814306693)
I will be removed soon

从输出中,我们可以看到添加新事件侦听器时的日志语句。 然后我们调用 buy() 函数,两个监听器都会对它做出反应。 第一个侦听器发送电子邮件并将数据保存到数据库,然后我们的第二个侦听器将其消息 I will be removed soon 打印到屏幕上。

现在让我们使用 off() 函数来移除第二个事件监听器。 重新打开nano中的文件:

nano index.js

现在将以下 off() 调用添加到文件末尾。 您还将添加一条日志语句以确认侦听器的数量,并再次调用 buy()

事件发射器/index.js

...

ticketManager.off("buy", onBuy);

console.log(`We now have: ${ticketManager.listenerCount("buy")} listener(s) for the buy event`);
ticketManager.buy("test@email", 20);

请注意 onBuy 变量如何用作 off() 的第二个参数。 保存并退出文件。

现在用 node 运行它:

node index.js

之前的输出将保持不变,但这次我们将找到我们添加的新日志行,确认我们再次拥有一个侦听器。 当再次调用 buy() 时,我们只会看到第一个侦听器使用的回调的输出:

OutputWe have 1 listener(s) for the buy event
We have 1 listener(s) for the error event
We added a new event listener bringing our total count for the buy event to: 2
Sending email to test@email
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588816352178)
I will be removed soon
We now have: 1 listener(s) for the buy event
Sending email to test@email
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588816352178)

如果我们想用 off() 删除所有事件,我们可以使用 removeAllListeners() 函数。 此函数接受一个参数:我们要为其删除侦听器的事件的名称。

让我们在文件末尾使用这个函数来取消我们为 buy 事件添加的第一个事件侦听器。 再次打开 index.js 文件:

nano index.js

您将首先使用 removeAllListeners() 删除所有侦听器。 然后,您将使用 listenerCount() 函数记录带有侦听器计数的语句。 要确认它不再在听,您将购买另一张票。 当事件发出时,没有任何反应。

在文件末尾输入以下代码:

事件发射器/index.js

...

ticketManager.removeAllListeners("buy");
console.log(`We have ${ticketManager.listenerCount("buy")} listeners for the buy event`);
ticketManager.buy("test@email", 20);
console.log("The last ticket was bought");

保存并退出文件。

现在让我们使用 node 命令执行我们的代码:

node index.js

我们的最终输出是:

We have 1 listener(s) for the buy event
We have 1 listener(s) for the error event
We added a new event listener bringing our total count for the buy event to: 2
Sending email to test@email
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588817058088)
I will be removed soon
We now have: 1 listener(s) for the buy event
Sending email to test@email
Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588817058088)
We have 0 listeners for the buy event
The last ticket was bought

删除所有听众后,我们不再发送电子邮件或保存到数据库以进行购票。

结论

在本教程中,您学习了如何使用 Node.js 事件发射器来触发事件。 您使用 EventEmitter 对象的 emit() 函数发出事件,然后使用 on()once() 函数监听事件以在每次事件发生时执行代码触发。 您还为错误事件添加了一个侦听器,并使用 listenerCount() 函数监视和管理侦听器。

通过回调和承诺,我们的工单管理系统需要与电子邮件和数据库服务模块集成以获得相同的功能。 由于我们使用了事件发射器,因此事件与实现分离。 此外,任何可以访问工单管理器的模块都可以观察其事件并对其做出反应。 如果您希望内部或外部的 Node.js 模块来观察您的对象发生了什么,请考虑将其作为可伸缩性的事件发射器。

要了解有关 Node.js 中事件的更多信息,您可以阅读 Node.js 文档。 如果您想继续学习 Node.js,您可以返回 如何在 Node.js 中编写代码系列,或者在我们的 Node 主题页面 上浏览编程项目和设置。