何时应该使用EventEmitter?

28

我读了很多关于 EventEmitter 的内容,但不知道在我的 Node.js 应用程序中何时需要使用它。


可能是 https://dev59.com/3mYr5IYBdhLWcg3wuciw 的重复问题。 - Kishore
3个回答

70

每当代码需要订阅某些内容而不是从某些内容获取回调时,这是有意义的。典型的用例是当应用程序中有多个代码块需要在事件发生时执行某些操作。

例如,假设你正在创建一个票务系统。处理方式可能通常是这样的:

function addTicket(ticket, callback) {
    insertTicketIntoDatabase(ticket, function(err) {
        if (err)
            return handleError(err);

        callback();
    });
}

但现在,有人决定当一张票据被插入数据库时,应该通过电子邮件通知用户。这没问题,你可以将它添加到回调函数中:

function addTicket(ticket, callback) {
    insertTicketIntoDatabase(ticket, function(err) {
        if (err)
            return handleError(err);

        emailUser(ticket, callback);
    });
}

但现在,有人想要通知另一个系统票已经被插入。随着时间的推移,可能会发生许多该执行的操作。因此,让我们稍微修改一下:

function addTicket(ticket, callback) {
    insertTicketIntoDatabase(ticket, function(err) {
        if (err)
            return handleError(err);

        TicketEvent.emit('inserted', ticket);
        callback();
    });
}

我们不再需要等待所有这些函数完成后才通知用户界面。在您的代码其他位置,您可以轻松地添加这些函数:

TicketEvent.on('inserted', function(ticket) {
    emailUser(ticket);
});

TicketEvent.on('inserted', function(ticket) {
    notifySlack(ticket);
});

这个答案非常完美,采用了在这个答案中建议的“它允许您在整个应用程序中拥有多个侦听器”的想法 https://dev59.com/sabja4cB1Zd3GeqPe1md - Skadoosh
2
为什么要监听相同的事件,当你可以将它们全部放在一个监听器中呢? - alramdein
1
@alramdein 用于解耦。如果您想在插入票务后执行其他操作(除了Slack和电子邮件之外,比如向购票者发送短信),则无需更改现有代码。这是一个重要问题,属于“开闭原则” - 您的代码将关闭修改(甚至可能无法修改),但对扩展开放。 - Benny67b
这是我在所有主题中找到的最佳答案。非常感谢你。 - Mohsen Movahed
谢谢朋友,经过这么长时间我终于得到了。 - Raihanur Rahman

5
  • 当同一事件可能发生多次或根本不发生时,应该使用EventEmitter。实际上,期望回调被调用恰好一次,无论操作是否成功。回调意味着在准备就绪时给我打电话。

  • 使用回调的API只能通知一个特定的回调函数,而使用EventEmitter可以注册多个监听器以处理相同的事件。

  • 如果需要通知用户状态更改,请使用事件发射器。

  • 为了测试目的,如果要确保在函数内调用另一个函数,请发出事件。


4
Node.js事件发射器用于将代码库分解为组件或服务,并使用类似于发布-订阅的异步模式来调用它们。但是,通常当我们谈论发布-订阅模式时,我们指的是分布式分解系统。在这里并非如此,因为所有组件都存在于同一个代码仓库中,并在同一个Node.js运行时中运行。
请注意,使用Node.js事件发射器并不会自动使我们的代码非阻塞、异步化。需要特别注意的是,事件监听器(订阅者)不应相互阻塞,即事件监听器应该异步执行代码。
此外,在使用此模式时,事件发射器(发布者)不关心事件监听器执行操作的结果。没有回调或返回值。如果这些操作很关键,则必须处理故障。
代码示例:
/**
 * When event listeners execute synchronous blocking code as seen in this example,
 * the next listener is not notified until the first listener completes execution
 * of the synchronous blocking code.
 *
 * Here is an output from running this code:
 *
 * 11:16:40 Listener 1 - processing event
 * 11:16:45 Listener 1 - processed: Test Event
 * 11:16:45 Listener 2 - processing event
 * 11:16:45 Listener 2 - processed: Test Event
 */

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

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 1 - processing event`);
  for (let i = 0; i < 6e9; i += 1) {
    // Intentionally empty
  }
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');


/**
 *
 * To take full advantage of EventListener the listeners should execute
 * asynchronous non-blocking code. However, wrapping a synchronous code
 * into an async function is not enough. The 2nd listener is still
 * blocked and waiting for the async function to complete
 *
 * Here is an output from running this code:
 * 11:13:52 Listener 1 - processing event
 * 11:13:52 Listener 1 - about to await
 * 11:13:57 Listener 2 - processing event
 * 11:13:57 Listener 2 - processed: Test Event
 * 11:13:57 Listener 1 - await completed
 * 11:13:57 Listener 1 - processed: Test Event
 */

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

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', async (message) => {
  console.log(`${time()} Listener 1 - processing event`);

  async function extracted() {
    for (let i = 0; i < 6e9; i += 1) {
      // Intentionally empty
    }
  }

  console.log(`${time()} Listener 1 - about to await`);
  await extracted();
  console.log(`${time()} Listener 1 - await completed`);
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');

/**
 *
 * To take full advantage of EventListener the listeners should execute
 * asynchronous non-blocking code. Here we are using setTimeout() in order
 * to execute code asynchronously.
 *
 * Here is an output from running this code:
 *
 * 11:45:54 Listener 1 - processing event
 * 11:45:54 Listener 1 - about to execute setTimeout
 * 11:45:54 Listener 1 - setTimeout completed
 * 11:45:54 Listener 1 - processed: Test Event
 * 11:45:54 Listener 2 - processing event
 * 11:45:54 Listener 2 - processed: Test Event
 * 11:45:59 Listener 1 - finished the long loop
 */

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

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', async (message) => {
  console.log(`${time()} Listener 1 - processing event`);

  function extracted() {
    for (let i = 0; i < 6e9; i += 1) {
      // Intentionally empty
    }
    console.log(`${time()} Listener 1 - finished the long loop`);
  }

  console.log(`${time()} Listener 1 - about to execute setTimeout`);
  setTimeout(extracted, 0);
  console.log(`${time()} Listener 1 - setTimeout completed`);
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');

/**
 *
 * To take full advantage of EventListener the listeners should execute
 * asynchronous non-blocking code. Here we are using setImmediate() in order
 * to execute code asynchronously.
 *
 * Here is an output from running this code:
 *
 * 12:1:3 Listener 1 - processing event
 * 12:1:3 Listener 1 - about to execute setImmediate
 * 12:1:3 Listener 1 - setImmediate completed
 * 12:1:3 Listener 1 - processed: Test Event
 * 12:1:3 Listener 2 - processing event
 * 12:1:3 Listener 2 - processed: Test Event
 * 12:1:9 Listener 1 - finished the long loop
 */

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

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', async (message) => {
  console.log(`${time()} Listener 1 - processing event`);

  function extracted() {
    for (let i = 0; i < 6e9; i += 1) {
      // Intentionally empty
    }
    console.log(`${time()} Listener 1 - finished the long loop`);
  }

  console.log(`${time()} Listener 1 - about to execute setImmediate`);
  setImmediate(extracted);
  console.log(`${time()} Listener 1 - setImmediate completed`);
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');

1
在Node.js官方网站上,"使用异步函数与事件处理程序存在问题,因为它可能导致未处理的拒绝,如果出现异常情况"。 - ZenG
1
在Node.js官方网站上,使用异步函数与事件处理程序是有问题的,因为它可能导致在抛出异常的情况下出现未处理的拒绝。 - undefined
长时间运行的阻塞操作,无论是同步还是异步,都不重要,因为Node.js是单线程的。 - ZenG
对于长时间运行的阻塞操作来说,无论是同步还是异步,都无关紧要,因为Node.js是单线程的。 - undefined
因此,可以接受同步。 - ZenG
在Node.js中处理长时间运行的任务应该使用工作线程来处理。 - ZenG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接