Cómo usar eventos en Node.js de la manera correcta

Antes de que la programación impulsada por eventos se hiciera popular, la forma estándar de comunicarse entre diferentes partes de una aplicación era bastante sencilla: un componente que quería enviar un mensaje a otro invocó explícitamente un método en ese componente. Pero el código controlado por eventos está escrito para reaccionar en lugar de ser llamado .

Los beneficios de los eventos

Este enfoque hace que nuestros componentes estén mucho más desacoplados . A medida que continuamos escribiendo una aplicación, identificamos eventos en el camino. Los disparamos en el momento adecuado y adjuntamos uno o más oyentes de eventos a cada uno. Ampliar la funcionalidad se vuelve mucho más fácil. Podemos agregar más oyentes a un evento en particular. No estamos alterando los oyentes existentes o la parte de la aplicación desde la que se disparó el evento. De lo que estamos hablando es del patrón Observer.

Diseñar una arquitectura impulsada por eventos

Identificar eventos es bastante importante. No queremos terminar teniendo que eliminar / reemplazar eventos existentes del sistema. Esto podría obligarnos a eliminar / modificar cualquier número de oyentes que estuvieran adjuntos al evento. El principio general que utilizo es considerar la activación de un evento solo cuando una unidad de lógica empresarial finaliza la ejecución.

Digamos que desea enviar varios correos electrónicos diferentes después del registro de un usuario. Ahora, el proceso de registro en sí puede implicar muchos pasos complicados y consultas. Pero desde el punto de vista empresarial, es un paso. Y cada uno de los correos electrónicos que se enviarán también son pasos individuales. Por lo tanto, tendría sentido iniciar un evento tan pronto como finalice el registro. Tenemos varios oyentes adjuntos, cada uno responsable de enviar un tipo de correo electrónico.

La arquitectura asincrónica impulsada por eventos de Node tiene ciertos tipos de objetos llamados "emisores". Emiten eventos con nombre que hacen que se invoquen funciones llamadas "oyentes". Todos los objetos que emiten eventos son instancias de la clase EventEmitter. Utilizándolo, podemos crear nuestros propios eventos:

Un ejemplo

Usemos el módulo de eventos incorporado (que le animo a que consulte en detalle) para obtener acceso EventEmitter.

Esta es la parte de la aplicación donde nuestro servidor recibe una solicitud HTTP, guarda un nuevo usuario y emite un evento:

Y un módulo separado donde adjuntamos un oyente:

Es una buena práctica separar la política de la implementación. En este caso, política significa qué oyentes están suscritos a qué eventos. Implementación significa los propios oyentes.

Esta separación permite que el oyente también sea reutilizable. Puede adjuntarse a otros eventos que envían el mismo mensaje (un objeto de usuario). También es importante mencionar que cuando se adjuntan varios oyentes a un solo evento, se ejecutarán sincrónicamente y en el orden en que se adjuntaron . Por someOtherListenerlo tanto, se ejecutará después de que sendEmailOnRegistrationfinalice la ejecución.

Sin embargo, si desea que sus oyentes se ejecuten de forma asincrónica, simplemente puede envolver sus implementaciones con setImmediateesto:

Mantenga limpios a sus oyentes

Cíñete al principio de responsabilidad única al escribir a los oyentes. Un oyente debe hacer una sola cosa y hacerlo bien. Evite, por ejemplo, escribir demasiados condicionales dentro de un oyente que decidan qué hacer dependiendo de los datos (mensaje) que fue transmitido por el evento. Sería mucho más apropiado usar diferentes eventos en ese caso:

Separar a los oyentes explícitamente cuando sea necesario

En el ejemplo anterior, nuestros oyentes eran funciones totalmente independientes. Pero en los casos en los que un oyente está asociado con un objeto (es un método), debe separarse manualmente de los eventos a los que se había suscrito. De lo contrario, el objeto nunca será recolectado como basura, ya que una parte del objeto (el oyente) seguirá siendo referenciada por un objeto externo (el emisor). De ahí la posibilidad de una fuga de memoria.

Por ejemplo, si estamos creando una aplicación de chat y queremos que la responsabilidad de mostrar una notificación cuando llegue un nuevo mensaje a una sala de chat a la que se haya conectado un usuario se encuentre dentro de ese objeto de usuario, podríamos hacer esto:

Cuando el usuario cierra su pestaña o pierde su conexión a Internet por un tiempo, naturalmente, es posible que deseemos lanzar una devolución de llamada en el lado del servidor que notifique a los otros usuarios que uno de ellos acaba de desconectarse. En este punto, por supuesto, no tiene ningún sentido displayNewMessageNotificationque se invoque para el usuario sin conexión. Se seguirá invocando en mensajes nuevos a menos que lo eliminemos explícitamente. Si no lo hacemos, aparte de la llamada innecesaria, el objeto de usuario también permanecerá en la memoria indefinidamente. Así que asegúrese de llamar disconnectFromChatrooma su devolución de llamada del lado del servidor que se ejecuta cada vez que un usuario se desconecta.

Tener cuidado

El acoplamiento flexible en arquitecturas controladas por eventos también puede conducir a una mayor complejidad si no tenemos cuidado. Puede ser difícil realizar un seguimiento de las dependencias en nuestro sistema. Nuestra aplicación se volverá especialmente propensa a este problema si comenzamos a emitir eventos desde los oyentes. Esto posiblemente podría desencadenar cadenas de eventos inesperados.