Implementando Observables a partir de Eventos en JS

Nos encontramos en la ola de la programación reactiva y funcional, y a pesar de todas la ventajas que ofrece aún es desconocida para la mayoría de desarrolladores sobre todo aquellos desarrolladores jóvenes. En éste post vamos a tratar de implementar la definición más pura de observable y la función generadora de observables más común como ya lo tiene implementado RX.js con el fin de entender a profundidad el concepto.

La manera más natural de acercarse a los observables es entendiendo que es la combinación de dos patrones de diseño en Javascript: Uno es el patrón iterador que lo explico en este post, y el segundo es el patrón observer que también lo explico en este post, sin embargo no es la manera más fácil. Una vez que hayas leido estos dos patrones y al menos tengas una idea en la cabeza de lo que son y significan un observable no es más que una función en Javascript con otra función en su interior que retorna un Objeto (JSON) con tres funciones en su interior. onNext, onComplete y onError. En primer lugar escribamos en Javascript lo que te acabo de narrar:

  • js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ES5 & 6 (Antiguo estándar)
function Observable(forEach) {
this._forEach = forEach;
}

Observable.prototype = {
forEach: function(onNext, onError, onCompleted) {
if (typeof onNext === "function") {
/* la función se invocó explicitamente con
* (onNext: ()=> ...., onError: () => ....., onComplete: () => ....) */
return this._forEach({
onNext: onNext,
onError: onError || function(){},
onCompleted: onCompleted || function(){}
})
}
else {
/* la función se invocó explicitamente con un objeto en lugar de tres funciones
* {onNext: ()=> ...., onError: () => ....., onComplete: () => ....} */
return this._forEach(onNext);
}
}
}

Como te dije es una función llamada Observable que recibe como parámetro una función que en este caso la llamamos forEach, ¡Y Sí! Tiene todo que ver con la función ForEach que usamos con los arreglos en Javascript. Como verás esta función la implementamos de manera interna en el Observable para hacerla propia de la definición del Observable. Como verás en la invocación de la función forEach, asumimos que la van a invocar enviándole tres parámetros, onNext, onError y onComplete y que lo único que haremos será detectar si han enviado cada función como parámetro o si han enviado las tres funciones dentro un objeto JSON, y en cualquier caso devolvemos recursivamente la invocación del forEach nuevamente con la declaración en su interior.

Wait what? …

Sí, tal vez esto hasta aquí te estalló un poco la cabeza, la programación con observables suele cambiar el paradigma con el cual escribimos código, trataré de mostrarte un ejemplo para que sirve lo que hemos hecho escrito allí arriba, pero antes añadamos una función al enredado bloque de código de arriba. Imagínate que quieres mostrar un console.log(“Hizo click”); cada vez que un usuario hace click en un botón de tu sitio web. Para ello vamos a crear una función estática dentro de la definición de observable que creamos arriba que convertirá un evento del DOM en un observable:

  • js
1
2
3
4
5
6
7
8
9
10
11
Observable.fromEvent = function(dom, eventName) {
return new Observable(function forEach(observer) {
var handler = (e) => observer.onNext(e);
dom.addEventListener(eventName, handler);
return {
dispose: () => {
dom.removeEventListener(eventName, handler)
}
}
});
}

Como verás la función fromEvent recibe un elemento del DOM y el nombre del evento sobre el que queremos hacer seguimiento y retorna un observable sobre que nos retorna el evento cada vez que se ejecuta la acción dentro de la función onNext. Adicionalmente un Observable siempre debe retornar un objeto que llamaremos subscripción que tiene la responsabilidad de proveer una manera de dessuscribirnos al observable en caso de que no deseemos recibir mas información esta es la responsabilidad de la función dispose. Ahora veamos como usamos los dos bloques de código juntos para capturar los eventos cada vez que hagamos click sobre un botón.

  • js
1
2
3
4
5
const button = document.getElementById("button");
const clicks = Observable.fromEvent(button, "click");
const clicksSubscription = clicks.forEach( event => {
console.log("El usuario hizo click", event);
});

Bien veamos ahora como podemos hacer lo mismo usando completamente ES7, lo primero que tenemos que tener presente es que la función forEach por convención es reemplazada por la función subscribe. Y nos valdremos de las palabras reservadas class y static para crear nuestra implementación de observable. Veamos entonces en primer lugar como implementamos la definición pura de observable:

  • js
1
2
3
4
5
6
7
8
class Observable {  
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(observer) {
return this._subscribe(observer);
}
}

Como verás aprovechándonos de la definición de clase y constructor se hace mas corta la implementación del observable. Ademas en esta nueva definición no nos importa en absoluto como el observador (observer) envía las funciones onNext, onComplete y onError (Que por cierto por convención se cambian a next, error y complete). Veamos entonces como implementamos la función fromEvent para crear observables a partir de eventos.

  • js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Observable {

static fromEvent(domElement, eventName) {
return new Observable(function subscribe(observer) {
const handler = ev => { observer.next(ev) };
domElement.addEventListener(eventName, handler);
return {
unsubscribe() {
domElement.removeEventListener(eventName, handler);
}
}
});
}
}

Análogamente la ejecución del observable se realiza de manera similar a la ejecución del observable en el antiguo estándar con la diferencia que en lugar de usar la función forEach, usamos la función subscribe:

  • js
1
2
3
4
5
6
7
const button = document.getElementById("button");
const clicks = Observable.fromEvent(button, "click");
const clicksSubscription = clicks.subscribe({
next(e) {
console.log("Click in the button");
}
});

Como verás en los dos bloques de código anteriores notaras que la diferencia entre el estándar viejo o antiguo para crear la definición pura más simple de un observable es significativamente mas larga y más descriptiva que el estándar nuevo y esto se debe a que usando el estándar nuevo dejamos por completo la implementación de las funciones onNext, onComplete y onError al observador que en adelante serán llamadas next, complete y error (sin el “on” al comienzo) verás que la invocación y ejecución del observable es muy similar usando ambos estándares.

Como notarás un observable no se ejecuta a menos que tengas un subscriptor que este escuchando los eventos mediante el subscribe o el forEach respectivamente y esto se debe a que por naturaleza los observables son fríos (Cold or lazy observables) es decir que no hacen nada a menos que tengan a alguien que los escuche.

Finalmente para dessuscribirte de un Observable puedes usar el método dispose que o unsubscribe que viene junto con la subscripción de la siguiente manera:

  • js
1
2
3
4
5
// Estandar antiguo
clicksSubscription.dispose();

// Estándar nuevo
clicksSubscription.unsubscribe();

Sin embargo recuerda que si te dessuscribes antes de escuchar por el observable no obtendrás ningún evento, es decir una implementación como la siguiente solo nos permitiría obtener únicamente el primer click sobre el botón:

  • js
1
2
3
4
5
6
const clicksSubscription = clicks.subscribe({
next(e) {
console.log("Click in the button");
clicksSubscription.unsubscribe();
};
});

En los siguientes links encontrarás la implementación:

Eso es todo, espero que este post te sea de utilidad y lo puedas aplicar a algún proyecto que tengas en mente y que simplemente te haya ayudado a entender la naturaleza de los observables. déjame un comentario si lograste implementarlo, si quieres añadir alguna otra funcionalidad o si tienes alguna duda no dudes en dejarme un comentario en la parte de abajo, recuerda que si te gustó también puedes compartir usando los links a las redes sociales en la parte de abajo.

Copyrights © 2018 Sebastian Gomez. All Rights Reserved.

Sobre mí

sebastianMi nombre es Sebastián Gómez, soy ingeniero de sistemas e Informática y Magister en Ingeniería de Sistemas de la Universidad Nacional de Colombia.

Actualmente trabajo en Globant como Web UI Developer con énfasis en aplicaciones híbridas y cross compiladas. Soy el organizador del Google Developers Group de Medellín, así que contactame si quieres dar alguna charla o participar actuamente de esta comunidad.

He participado en una Startup Colombiana llamada SponzorMe al lado de Carlos Rojas y fuí participante de Startup Chile a pesar de no haber continuado con esta startup me apasiona el emprendimiento y me gusta aconsejar y ayudar startups como mentor técnico. También he trabajado en empresas Americanas como StudioHyperset en Estados Unidos y para Measured Medium. Mi interés y mi experiencia es el desarrollo de web y móvil full stack como Front-end con Javascript. Me apasiona desarrollar software, escribir código y enseñar lo que aprendo día a día.

También he trabajado como profesor en diferentes universidades en Medellín Colombia, con tematicas relacionadas con la Inteligencia Artificial, Bases de datos, programación orientada a objetos, minería de datos, desarrollo de software, desarrollo móvil y desarrollo web.

Me encanta escribir código rápido y prototipar de una manera accelerada si quieres ver que hago día a día puedes darle un vistazo a mi codepen:  https://codepen.io/seagomezar/.

Todos los días trato de crear o participar en proyectos, la mayoría open source, así que puede chequear mi GitHub:  https://github.com/seagomezar.

Mi áreas de investigación académica son: Ingeniería de software, Ingeniería de requisitos, procesamiento del lenguaje natural, Ontologías, Bases De Datos,  Machine Learning, Seguimiento de trayectorias y Modelamiento matemático de formaciones.

Estas son algunas de mis publicaciones académicas mas recientes: