EventBus con JavaScript: desacoplando nuestro código

EventBus es una implementación de publish/suscribe que nos va a permitir desacoplar lógica entre los diversos componentes de nuestros proyectos.

Rodríguez Patiño, Eduardo
2020-11-27 | 279 lecturas

EventBus es una implementación de publish/suscribe que nos va a dar un gran aporte si lo usamos de manera correcta para desacoplar la lógica de nuestro proyecto.

¿Cómo funciona?

La teoría es simple, un componente o módulo se suscribe a un evento (suscriber) para estar atento a quien emite un mensaje (publisher).

Suscriptor

Es el encargado de estar atento a los eventos producidos y tomar la decisión que crea correspondiente.

Publicador

Tiene la repsonsabilidad de emitir los mensajes al suscriptor para que este pueda tomar una decisión.

Ejemplo

Supongamos que tenemos un módulo ShoppingCart que gestiona el carrito de compra de nuestros usuarios y cada vez que se agrega un nuevo producto, queremos actualizar el contador de items agregados al carrito.

class ShoppingCart{
    constructor() {
    this.items = [];
  }

    add(item) {
    this.items.push(item);

    document.getElementById('shoppingCartCounter').innerText = this.items.length;
  }
}

Para hacer uso de esta clase ahora tenemos que instanciarla, quedando como resultado lo siguiente:

const shoppingCart = new ShoppingCart();

function addToCartOnClick(productId) {
  shoppingCart.add(productId);
};

Y en el HTML se vería algo así:

<ul>
  <li><a onclick="addToCartOnClick(1)">Producto A</a></li>
  <li><a onclick="addToCartOnClick(2)">Producto B</a></li>
  <li><a onclick="addToCartOnClick(3)">Producto C</a></li>
</ul>

El problema

Esto funciona bien, pero ¿qué pasaría si necesitamos usar la misma lógica desde otro lugar?. Pues nuestro nuevo módulo va a tener que hacer otra instancia de la clase ShoppingCart y pasaría lo siguiente:

Contador erróneo

Porque el módulo A y B van a manejar su propia instancia de ShoppingCart, cuando en realidad el contador debería ser la suma de todos los productos agregados sin importarle de que módulo venga.

Bien, esto se puede solucionar si nuetros nuevos módulos reciben la instancia de ShoppingCart en vez de crearlos ellos mismos pero tendríamos una fuerte dependencia.

Algo como esto no?

function addToCartOnClick(shoppingCart, productId) {
  shoppingCart.add(productId);
};

Pues no lo creo, supongamos que más adelante necesitamos más dependencias, ¿pasamos más parámetros?.

La solución

Vamos a crear una clase simple para gestionar los eventos.

class EventBus {
    constructor() {
        this.subscriptions = {};
    }

    emit(event, value) {
        this.subscriptions[event](value);
    }

    on(event, callback) {
        if (this.subscriptions[event]) {
            throw new Error(`${event} has been already registered.`);
        }

        this.subscriptions[event] = callback;
    }

    off(event) {
        delete this.subscriptions[event];
    }
}
  • emit: emite o dispara el evento.
  • on: suscribe un evento y la acción a realizar.
  • off: apaga un evento.

Ahora actualizamos nuestra clase ShoppingCart para que se suscriba al evento.

class ShoppingCart{
    constructor(eventBus) {
        this.items = [];

        eventBus.on('add-to-cart', (value) => {
            this.add(value);
        });
    }

    add(item) {
        this.items.push(item);    
        document.getElementById('shoppingCartCounter').innerText = this.items.length;
    }
}

Y la implementación quedaría así:

const eventBus = new EventBus();
const shoppingCart = new ShoppingCart(eventBus);

function addToCartOnClick(productId) {
  eventBus.emit('add-to-cart', productId);
}

Si quieren que otro módulo pueda comunicarse con ShoppingCart solo deberán transporar el objeto eventBus.

Conclusiones

Hoy en día que la tendencia es hacer uso de componentes ya sea como Vue, React o Angular, el uso de eventBus nos va a permitir comunicar varios componentes sin tener que conocerse entre ellos.

Por otro lado, la clase que hemos creado solo permite un suscriptor por evento, podemos mejorar este código para que trabaje con varios suscriptores.