Ir al contenido
angel.library

Observer

Si el código que crea un usuario tiene que llamar directamente a enviarBienvenida(), registrarEnAnalytics() y sincronizarConCRM(), ese servicio termina acoplado a todo lo que “reacciona” a la creación de un usuario. Agregar un quinto listener implica tocar ese archivo otra vez. Observer invierte la relación: el que dispara el evento no conoce a los que escuchan.

lib/events.ts
import { EventEmitter } from "node:events"
export const eventos = new EventEmitter()
services/users.service.ts
import { eventos } from "@/libs/events"
export async function crearUsuario(datos: DatosUsuario) {
const usuario = await db.usuario.create({ data: datos })
eventos.emit("user:created", { id: usuario.id, email: usuario.email })
return usuario
}
listeners/on-user-created.ts
import { eventos } from "@/libs/events"
eventos.on("user:created", ({ email }) => enviarBienvenida(email))
eventos.on("user:created", ({ id }) => registrarEnAnalytics(id))

crearUsuario no sabe (ni le importa) cuántos listeners hay ni qué hacen. Agregar uno nuevo es agregar un .on() en otro archivo.

  • addEventListener('click', handler) del DOM es Observer: el botón no sabe qué código reacciona al click.
  • Una store de Zustand o Redux notifica a cada componente suscrito cuando el estado cambia — los componentes son observers del store.
const useStore = create<Estado>((set) => ({
count: 0,
incrementar: () => set((s) => ({ count: s.count + 1 }))
}))
// Cada componente que llama useStore() se suscribe y re-renderiza cuando el estado cambia.

Si solo hay un emisor y un único receptor que siempre reacciona, una llamada de función directa es más fácil de seguir que un evento. Los eventos dificultan trazar “quién reacciona a esto” cuando se abusan en un codebase grande — reservalos para cuando de verdad hay múltiples receptores desacoplados, o cuando el emisor no debería conocer a sus consumidores.