什么时候应该使用 EventEmitter?

When should I use EventEmitter?

我读了很多关于 EventEmitter 的东西。但是我不知道在什么情况下我需要在我的 Node.js 应用程序中使用它。

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

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

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);
});

Node.js 当希望将代码库分解为 使用类似于异步模式的 pub-sub 调用的组件或服务。 但是,通常当我们谈论发布-订阅模式时,我们指的是分布式分解系统。这不是这里的情况,因为所有组件都存在于相同的代码存储库中,并且 运行 在相同的 Node.js 运行 时间。

请记住,使用 Node.js 事件发射器不会使我们的代码自动非阻塞、异步。需要特别注意事件监听器(订阅者)不会互相阻塞,即事件监听器应该异步执行代码。

此外,当使用此模式时,事件发射器(发布者)不关心事件侦听器采取的操作的结果。没有回调或 return 值。如果这些操作很关键,则需要处理故障。

代码示例:

/**
 * 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');
  • 当同一个事件可以多次发生时,应该使用EventEmitter 次,或者根本不会发生。实际上,预期会调用回调 恰好一次,无论操作是否成功。回调意味着当你准备好时给我打电话

  • 使用回调的 API 只能通知一个特定的回调,同时 使用 EventEmitter 允许我们为 同一事件。

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

  • 出于测试目的,如果您想确保函数在函数内部被调用,请发出事件。