Sebastian Gomez
Implementando map como operador en Observables
En este post vamos a explorar cómo aumentar el número de operadores que podemos implementar y usar dentro de un Observable. Primero, recordemos cuál es la definición más pura de un Observable, con una función que genera Observables a partir de eventos:
// Define la clase "Observable"
class Observable {
// Constructor de la clase "Observable" que acepta un argumento "subscribe"
constructor(subscribe) {
// Asigna el valor del argumento "subscribe" a la propiedad "_subscribe" del objeto
this._subscribe = subscribe;
}
// Método "subscribe" que acepta un objeto "observer"
subscribe(observer) {
// Llama a la función almacenada en la propiedad "_subscribe" y le pasa el objeto "observer"
return this._subscribe(observer);
}
// Define un método estático "fromEvent" que acepta dos argumentos: "domElement" y "eventName"
static fromEvent(domElement, eventName) {
// Retorna una nueva instancia de la clase "Observable"
return new Observable(function subscribe(observer) {
// Define una función "handler" que acepta un argumento "ev"
const handler = (ev) => {
// Llama al método "next" del objeto "observer" y le pasa el argumento "ev"
observer.next(ev);
};
// Añade un event listener al elemento DOM "domElement" que escucha el evento
// especificado en "eventName" y llama a "handler" cuando el evento ocurre
domElement.addEventListener(eventName, handler);
// Retorna un objeto que contiene un método "unsubscribe"
return {
// Define el método "unsubscribe"
unsubscribe() {
// Elimina el event listener del elemento DOM asociado con "eventName" y "handler"
domElement.removeEventListener(eventName, handler);
},
};
});
}
}Con esto podemos crear Observables que nos devuelven la información del evento de la siguiente manera:
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 desuscribirAhora, imaginemos que queremos hacer más operaciones sobre la cadena de datos, como obtener solo algunos datos del evento, o escuchar solo los clicks que se hagan en la parte izquierda del botón. Vamos a implementar la función map sobre Observables.
La función map es una función pura que itera sobre cada elemento de un array y "mapea" cada ítem de un array en otro:
[1, 2, 3].map(elemento => elemento * 2); // Retorna [2, 4, 6]Para implementar map en Observables, haremos lo siguiente:
class Observable {
// ...
map(projection) {
const self = this;
return new Observable(function subscribe(observer) {
let subscription;
subscription = self.subscribe({
next(v) {
let value;
try {
value = projection(v);
observer.next(value);
}
catch (e) {
observer.error(e);
// Protegemos la limpieza: durante la primera emisión síncrona
// "subscription" todavía puede no estar asignado.
if (subscription) subscription.unsubscribe();
}
},
error(e) {
observer.error(e);
},
complete() {
observer.complete();
}
});
return subscription;
});
}
// ...
}Un detalle importante de este código: dentro del catch llamamos a subscription.unsubscribe(), pero subscription es la misma variable que estamos asignando con esa llamada a self.subscribe({...}). Si la proyección lanza un error durante la primera emisión síncrona, subscription todavía no estaría asignado y la limpieza fallaría con un segundo error. Por eso lo protegemos con if (subscription). En el ejemplo de fromEvent los clicks son asíncronos, así que rara vez se da el caso, pero es una buena costumbre dejar la limpieza a prueba de fallos.
Y ahora podemos usar map para transformar los datos dentro de nuestro Observable, obteniendo solamente la posición del mouse en el momento en que se hizo click sobre el botón:
const button = document.getElementById("button");
const clicks = Observable.fromEvent(button, "click");
const clicksSubscription = clicks
.map((ev) => ev.offsetX)
.subscribe({
next(offSetX) {
console.log(offSetX);
},
});Con estos fragmentos tienes la definición completa: la clase Observable con su constructor y subscribe, el método estático fromEvent y el operador map. Te recomiendo copiarlos en un editor en línea como CodePen o CodeSandbox y experimentar con ellos para verlos en acción.
Conclusiones
- Hemos implementado la función
mapen Observables, lo que nos permite realizar transformaciones en los datos de eventos de una manera más flexible. mapen Observables es similar a su uso en arrays, permitiendo aplicar una función de proyección a cada elemento.- Al usar
mapen Observables, podemos realizar operaciones más avanzadas y personalizadas en la cadena de datos, lo que mejora la calidad y la eficiencia de nuestro código.
Ejercicios para practicar
- Implementa la función
filteren Observables para permitir filtrar eventos basados en una condición específica. - Crea un Observable que devuelva eventos de teclado y usa
mappara transformar la salida en objetos que contengan solo la tecla presionada y el momento en que ocurrió el evento. - Combina el uso de
mapyfilteren Observables para procesar eventos de mouse y devolver solo la información relevante de aquellos eventos que cumplan ciertos criterios.
Resumen en 3 puntos
- Hemos ampliado nuestra implementación de Observables con el operador
map, lo que nos permite transformar los datos de los eventos de forma más flexible y eficiente. - La función
mapen Observables es similar a su uso en arrays, aplicando una función de proyección a cada elemento de la secuencia. - Con
mappodemos realizar transformaciones avanzadas y personalizadas en la cadena de datos, lo que nos permite manejar eventos de manera más efectiva en nuestras aplicaciones.
Espero que este post te haya sido útil y te haya ayudado a comprender cómo implementar el operador map en Observables. Si tienes alguna pregunta o comentario, no dudes en dejarlo en la sección de comentarios. Buena suerte en tus proyectos y no olvides seguir aprendiendo y practicando.
Si te gustó este post, no olvides compartirlo con tus amigos y colegas a través de las redes sociales utilizando los enlaces de aquí abajo. ¡Hasta la próxima!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.