如何正确使用Node.js事件

翻译:疯狂的技术宅
原文: https://medium.freecodecamp.o...

clipboard.png


本文首发微信公众号:前端先锋
欢迎关注,天天都给你推送新鲜的前端技术文章javascript


事件驱动的编程变得流行以前,在程序内部进行通讯的标准方法很是简单:若是一个组件想要向另一个发送消息,只是显式地调用了那个组件上的方法。可是在 react 中用的倒是事件驱动而不是调用html

事件的好处

这种方法可以使组件更加分离。在咱们继续写程序时,会识别整个过程当中的事件,在正确的时间触发它们,并为每一个事件附加一个或多个事件监听器,这使得功能扩展变得更加容易。咱们能够为特定事件添加更多的 listener,而没必要修改现有的侦听器或触发事件的应用程序部分。咱们所谈论的是观察者模式。前端

clipboard.png

设计一个事件驱动的体系结构

对事件进行识别很是重要,咱们不但愿最终必须从系统中删除或替换现有事件,由于这可能会迫使咱们删除或修改附加到事件上的众多侦听器。个人通常原则是仅在业务逻辑单元完成执行时才考虑触发事件。java

假如你想在用户注册后发送一堆不一样的电子邮件。注册过程自己可能会涉及许多复杂的步骤和查询,但从商业角度来看,这只是其中的一个步骤。每一个要发送的电子邮件也是单独的步骤。所以,一旦注册完成立刻就发布事件是颇有意义的。因而咱们附加了多个监听器,每一个监听器负责发送一种类型的电子邮件。node

Node的异步事件驱动架构具备一些被称为“emitters”的对象。它们发出命名事件,这些事件会调用被称为“listener”的函数。发出事件的全部对象都是 EventEmitter 类的实例。使用它,咱们能够建立本身的事件:react

一个例子

让咱们使用内置的 events 模块(我建议你查看这个文档:https://nodejs.org/api/events...)以获取对 EventEmitter 的访问权限。程序员

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

module.exports = myEmitter;

这是咱们的服务器端程序的一部分,它负责接收HTTP请求,保存新用户并发出事件:面试

const myEmitter = require('./my_emitter');

// Perform the registration steps

// Pass the new user object as the message passed through by this event.
myEmitter.emit('user-registered', user);

附加一个监听器的单独模块:编程

const myEmitter = require('./my_emitter');

myEmitter.on('user-registered', (user) => {
  // Send an email or whatever.
});

将策略与实现分开是一种很是好的作法。在这种状况下,策略意味着哪些 listener 订阅了哪些事件。实现意味着 listener 本身。segmentfault

const myEmitter = require('./my_emitter');
const sendEmailOnRegistration = require('./send_email_on_registration');
const someOtherListener = require('./some_other_listener');

myEmitter.on('user-registered', sendEmailOnRegistration);
myEmitter.on('user-registered', someOtherListener);
module.exports = (user) => {
  // Send a welcome email or whatever.
}

这种分离使 listener 也能够被重复使用,它能够被附加到发送相同消息的其余事件上(用户对象)。一样重要的是 当多个 listener 被附加到单个事件时,它们将按照附加的顺序同步执行。所以 someOtherListener 将在 sendEmailOnRegistration 完成执行后运行。

可是,若是你但愿本身的 listener 以异步方式运行,只需用 setImmediate 包装它们的实现,以下所示:

module.exports = (user) => {
  setImmediate(() => {
    // Send a welcome email or whatever.
  });
}

让你的 Listeners 保持简洁

在写 listener 时要坚持单一责任原则。一个 listener 应该只作一件事并把事情作好。例如:要避免在 listener 中编写太多的条件并根据事件传来的数据(消息)去决定作什么。在这种状况下使用不一样的事件会更加合适:

const myEmitter = require('./my_emitter');

// Perform the registration steps

// The application should react differently if the new user has been activated instantly.
if (user.activated) {
  myEmitter.emit('user-registered:activated', user);
  
} else {
  myEmitter.emit('user-registered', user);
}
const myEmitter = require('./my_emitter');
const sendEmailOnRegistration = require('./send_email_on_registration');
const someOtherListener = require('./some_other_listener');
const doSomethingEntirelyDifferent = require('./do_something_entirely_different');


myEmitter.on('user-registered', sendEmailOnRegistration);
myEmitter.on('user-registered', someOtherListener);

myEmitter.on('user-registered:activated', doSomethingEntirelyDifferent);
view raw

必要时明确分离 Listener

在前面的例子中,咱们的 listener 是彻底独立的函数。可是在 listener 与对象关联的状况下(这时是一种方法),必须手动将其从已订阅的事件中分离出来。不然对象将永远不会被垃圾回收,由于对象( listener )的一部分将会继续被外部对象( emitter )引用,因此存在内存泄漏的可能。

例如,若是咱们正在开发一个聊天程序,而且但愿当新消息到达用户进入的聊天室时,显示通知的功能应该位于该用户对象自己的内部,咱们可能会这样作:

class ChatUser {
  
  displayNewMessageNotification(newMessage) {
    // Push an alert message or something.
  }
  
  // `chatroom` is an instance of EventEmitter.
  connectToChatroom(chatroom) {
    chatroom.on('message-received', this.displayNewMessageNotification);
  }

  disconnectFromChatroom(chatroom) {
    chatroom.removeListener('message-received', this.displayNewMessageNotification);
  }
}

当用户关闭他的标签或暂时断开互联网链接时,咱们可能但愿在服务器端发起一个回调,通知其余用户有人刚刚下线。固然在这时为脱机用户调用 displayNewMessageNotification 没有任何意义。除非咱们删除它,不然它将继续被用于调用新消息。若是不这样作,除了没必要要的调用以外,用户对象也会被永久地保留在内存中。所以在用户脱机时应该在服务器端回调中调用 disconnectFromChatroom

注意事项

若是不当心,即使是松散耦合的事件驱动架构也会致使复杂性的增长,可能会致使在系统中跟踪依赖关系变得很困难。若是咱们从侦听器内部发出事件,程序会特别容易出现这类问题。这可能会触发意外的事件链。


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:

相关文章
相关标签/搜索