Implementando map como operador en Observables

En el post anterior tuvimos un primer acercamiento a la definición más pura de lo que es un Observable y lo implementamos desde cero similarmente a como lo hace RX.js. En este post vamos a valernos de esta definición para aumentar el número de operadores que podemos implementar y usar dentro de un Observable. Primero recordemos cual es la definición más pura de un observable con una función generadora de observables a partir de eventos:

  • js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Observable {  
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(observer) {
return this._subscribe(observer);
}
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);
}
}
});
}
}

De esta manera tu puedes crear Observables que te devolverán información del evento de la siguiente manera:

  • js
1
2
3
4
5
6
7
8
9
const button = document.getElementById("button");
const clicks = Observable.fromEvent(button, "click");
const clicksSubscription = clicks.subscribe({
next(e) {
console.log("Click in the button”, e); // “e” contiene toda la información del evento
}
});

clicksSubscription.unsubscribe(); // De esta manera te puedes des-suscribir

Sin embargo es probable que necesitemos hacer más operaciones sobre la cadena de datos, imagínate si quizá requiramos solamente obtener sólo algunos datos del evento por ejemplo solo la posición del Mouse. O imagínate si sólo queremos escuchar por los clicks que se hagan en la parte izquierda del botón. Pues bien vamos a implementar dos funciones que quizá ya conozcas porque se usan principalmente para el procesamiento de arrays. Vamos a implementar la función “map” sobre observables. Pero antes veamos un ejemplo sobre como es su funcionamiento sobre Arrays en javascript para luego traerlo a nuestra definición de observables e implementarlo allí.

Empecemos con la función map. La función map es una función pura que itera sobre cada elemento de un array y “mapea” cada item de un array en otro. Veamos un ejemplo:

  • js
1
2
[1, 2, 3\].map( elemento => elemento * 2); 
// Retorna [1, 4, 6]

Ahora bien ¿Como hace internamente esta función para retornar este resultado? Básicamente la función map recibe como parámetro una función de proyección o transformación y aplica dicha función a cada uno de los elementos que pasan como parámetro. Esto hace que sin afectar el array original pueda devolver en nuevo arreglo con la función de proyección aplicada a cada item dentro del array. Veamos como sería la implementación de la función map para arrays:

  • js
1
2
3
4
5
6
7
Array.prototype.map = function(projectionFunction) {
const results = [];
this.forEach(function(itemInArray) {
results.push(projectionFunction(itemInArray));
});
return results;
};

¿Hermosa no? Ahora que te parece si nos arriesgamos a escribir lo mismo para observables. Similarmente que para arreglos, vamos a necesitar como parámetro una función de proyección que nos permita transformar cada valor que retorna el observable. Adicionalmente queremos retornar un observable también al que nos podamos subscribir. Empecemos por solo esas dos cosas que te acabó de decir:

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

map(projectionFunction) {
const self = this;
return new Observable(function subscribe(observer) {
const subscription = self.subscribe({
next(v) {
},
error (e) {
},
complete () {
}
});
return subscription;
});
}

}

Simple, ¿no? Como verás hemos definido la función map que ya no es “static” ya que no se puede aplicar sobre la definición abstracta de Observable sino sobre instancias de dicha clase. Adicionalmente recibimos como parámetro una función de proyección que es la que transformará cada valor que retorne la función interna next, y finalmente retornamos la subscripción para que sea posible suscribirnos al observable que justo acabamos de crear. Veamos entonces como podemos aplicar la función de proyección a cada elemento que retornemos dentro de la función next.

  • js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Observable {  

map(projectionFunction) {
const self = this;
return new Observable(function subscribe(observer) {
const subscription = self.subscribe({
next(v) {
const value = projectionFunction(v);
observer.next(value);
},
error (e) {
},
complete () {
}
});
return subscription;
});
}

}

Como ves no hicimos otra cosa que usar la función de transformación sobre cada valor que queremos enviar al observador. Eso es todo!, esta definida nuestra función map dentro de la definición que tenemos de Observable. Ajustemos un poco más esta definición previniendo posibles errores inesperados:

  • js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Observable {  

map(projection) {
const self = this;
return new Observable(function subscribe(observer) {
const subscription = self.subscribe({
next(v) {
let value;
try {
value = projection(v);
observer.next(value);
}
catch(e) {
observer.error(e);
subscription.unsubscribe();
}
},
error (e) {
observer.error(e);
},
complete () {
observer.complete();
}
});
return subscription;
});
}

}

Con lo anterior queda completa nuestra función map en la definición de observable. Veamos ahora como podemos usarla para transformar los datos dentro de nuestro observable, obteniendo solamente la posición del mouse en el momento que hizo click sobre el botón.

  • js
1
2
3
4
5
6
7
const button = document.getElementById("button");
const clicks = Observable.fromEvent(button, "click");
const clicksSubscription = clicks.map(ev =>ev.offsetX).subscribe({
next(offSetX) {
console.log(offSetX);
}
});

En el siguiente Codepen podrás revisar la definición completa:

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 del operador map sobre 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: