Implementando Observables a partir de Eventos en JS
Feb 26, 2022

Implementando Observables a partir de Eventos en JS

🚀 Implementando Observables a partir de Eventos en JS 🚀

En este post, vamos a explorar la implementación de observables y cómo la programación reactiva y funcional está cambiando la forma en que los desarrolladores abordan el código. ¡Prepárate para un viaje lleno de conocimientos y diversión! 🎉🧠

🌊 Programación reactiva y funcional 🌊

La programación reactiva y funcional es una tendencia en crecimiento en el mundo del desarrollo, pero aún no es ampliamente conocida, especialmente entre los desarrolladores más jóvenes. En este post, vamos a explorar la definición más pura de un observable y cómo implementar la función generadora de observables más común, tal como lo hace RX.js, para comprender el concepto en profundidad.

📚 Entendiendo los observables 📚

La mejor manera de acercarse a los observables es entender que son la combinación de dos patrones de diseño en JavaScript: el patrón iterador, que se explica en este post, y el patrón observer, que se explica en este post. Sin embargo, no es la forma más fácil. Después de leer estos dos patrones y tener una idea en mente de lo que son, 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: onNext, onComplete, y onError.

🔨 Implementando un observable en JavaScript 🔨

A continuación, se muestra el código de cómo implementar un observable en JavaScript:

// ES5 & 6 (Antiguo estándar)
// Esta línea indica que el código está escrito en las versiones ES5 y ES6 de JavaScript, que son estándares antiguos.

function Observable(forEach) {
  // Definimos la función constructora 'Observable' que toma un argumento 'forEach' y crea un nuevo objeto Observable.
  this._forEach = forEach;
  // Asignamos la función forEach recibida como argumento a la propiedad '_forEach' del objeto Observable.
}

Observable.prototype = {
  // Definimos las propiedades del objeto 'prototype' de la función constructora 'Observable'.
  forEach: function (onNext, onError, onCompleted) {
    // Declaramos un método 'forEach' en el prototipo de 'Observable' que toma tres argumentos: onNext, onError y onCompleted.
    if (typeof onNext === "function") {
      // Si el primer argumento 'onNext' es una función, entonces...
      return this._forEach({
        // ...ejecutamos la función almacenada en '_forEach' con un objeto que contiene las propiedades y sus valores.
        onNext: onNext,
        // 'onNext' se asigna al valor pasado como primer argumento.
        onError: onError || function () {},
        // Si se proporciona 'onError', se asigna a la propiedad 'onError'. De lo contrario, se asigna una función vacía.
        onCompleted: onCompleted || function () {},
        // Si se proporciona 'onCompleted', se asigna a la propiedad 'onCompleted'. De lo contrario, se asigna una función vacía.
      });
    } else {
      // Si el primer argumento 'onNext' no es una función, entonces...
      return this._forEach(onNext);
      // ...ejecutamos la función almacenada en '_forEach' con el primer argumento 'onNext' como parámetro.
    }
  },
};

Como puedes ver, es una función llamada Observable que recibe como parámetro una función llamada forEach. Esto tiene todo que ver con la función forEach que usamos con los arreglos en JavaScript. Esta función se implementa de manera interna en el Observable para hacerla propia de la definición del observable.

👀 Capturando eventos con observables 👀

Imagina que quieres mostrar un console.log("Hizo click"); cada vez que un usuario hace clic 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 antes, que convertirá un evento del DOM en un observable:

Observable.fromEvent = function (dom, eventName) {
  // Agregamos un método estático 'fromEvent' al objeto 'Observable'.
  // Este método toma dos argumentos: un elemento del DOM 'dom' y el nombre de un evento 'eventName'.
  return new Observable(function forEach(observer) {
    // La función crea y retorna un nuevo objeto 'Observable'.
    // La función 'forEach' que se pasa como argumento al constructor toma un 'observer' como parámetro.
    var handler = (e) => observer.onNext(e);
    // Creamos un 'handler' que es una función de flecha que recibe un evento 'e' y llama al método 'onNext' del 'observer', pasándole 'e'.
    dom.addEventListener(eventName, handler);
    // Registramos el 'handler' en el elemento del DOM 'dom' para el evento especificado en 'eventName'.
    return {
      // Retornamos un objeto que tiene una única propiedad 'dispose'.
      dispose: () => {
        // La propiedad 'dispose' es una función de flecha que...
        dom.removeEventListener(eventName, handler);
        // ...elimina el 'handler' previamente registrado en el elemento del DOM 'dom' para el evento especificado en 'eventName'.
      },
    };
  });
};

La función fromEvent recibe un elemento del DOM y el nombre del evento que queremos rastrear y retorna un observable que nos devuelve

const button = document.getElementById("button");
// Declaramos una constante 'button' y le asignamos el elemento del DOM con el ID "button".

const clicks = Observable.fromEvent(button, "click");
// Declaramos una constante 'clicks' y le asignamos el resultado de llamar al método estático 'fromEvent' de 'Observable',
// pasándole el elemento del DOM 'button' y el nombre del evento "click". Esto crea un objeto 'Observable' que escucha el
// evento de clic en el botón.

const clicksSubscription = clicks.forEach((event) => {
  // Declaramos una constante 'clicksSubscription' y le asignamos el resultado de llamar al método 'forEach' en el objeto
  // 'clicks'. Esta función toma una función de flecha como argumento que recibe un evento 'event'.
  console.log("El usuario hizo click", event);
  // Dentro de la función de flecha, registramos en la consola un mensaje "El usuario hizo click" seguido del objeto 'event'.
});

En este post, vamos a ver cómo podemos crear observables a partir de eventos utilizando ES7, las diferencias con el estándar antiguo y cómo suscribirse y cancelar la suscripción a los observables. ¡Acompáñanos en este emocionante viaje! 🎢🎡

🆕 Usando ES7 para observables 🆕

Al usar ES7, lo primero que debemos tener en cuenta es que la función forEach se reemplaza por convención por la función subscribe. Además, utilizaremos las palabras reservadas class y static para crear nuestra implementación de observable. Veamos cómo implementamos la definición pura de observable en ES7:

class Observable {
  // Declaramos una clase 'Observable'.
  constructor(subscribe) {
    // Definimos el constructor de la clase, que toma un argumento 'subscribe'.
    this._subscribe = subscribe;
    // Asignamos la función 'subscribe' recibida como argumento a la propiedad '_subscribe' de la instancia del objeto Observable.
  }
  subscribe(observer) {
    // Declaramos un método 'subscribe' en la clase 'Observable' que toma un argumento 'observer'.
    return this._subscribe(observer);
    // Ejecutamos la función almacenada en la propiedad '_subscribe' de la instancia del objeto Observable,
    // pasándole el argumento 'observer' y retornando el resultado.
  }
}

Gracias a la definición de clase y constructor, la implementación del observable es más corta. Además, en esta nueva definición no nos importa cómo el observador (observer) envía las funciones onNext, onComplete y onError (que por convención se cambian a next, error y complete).

💡 Implementando fromEvent en ES7 💡

Ahora, veamos cómo implementar la función fromEvent para crear observables a partir de eventos en ES7:

class Observable {
  // Declaramos una clase 'Observable'.

  // ...
  // Aquí se deben incluir el constructor y otros métodos que no se muestran en el fragmento de código proporcionado.

  static fromEvent(domElement, eventName) {
    // Declaramos un método estático 'fromEvent' en la clase 'Observable' que toma dos argumentos: un elemento del DOM 'domElement' y el nombre de un evento 'eventName'.
    return new Observable(function subscribe(observer) {
      // La función crea y retorna un nuevo objeto 'Observable'.
      // La función 'subscribe' que se pasa como argumento al constructor toma un 'observer' como parámetro.
      const handler = ev => { observer.next(ev) };
      // Creamos un 'handler' que es una función de flecha que recibe un evento 'ev' y llama al método 'next' del 'observer', pasándole 'ev'.
      domElement.addEventListener(eventName, handler);
      // Registramos el 'handler' en el elemento del DOM 'domElement' para el evento especificado en 'eventName'.
      return {
        // Retornamos un objeto que tiene una única propiedad 'unsubscribe'.
        unsubscribe() {
          // La propiedad 'unsubscribe' es una función que...
          domElement.removeEventListener(eventName, handler);
          // ...elimina el 'handler' previamente registrado en el elemento del DOM 'domElement' para el evento especificado en 'eventName'.
        }
      }
    });
  }
}

🖱️ Ejecución de observables 🖱️

La ejecución del observable se realiza de manera similar al estándar antiguo, pero en lugar de usar la función forEach, usamos la función subscribe:

// Creamos una constante llamada 'button' que almacena el elemento HTML con el ID "button"
const button = document.getElementById("button");

// Creamos una constante llamada 'clicks' que utiliza la librería 'RxJS' para observar el evento 'click' del elemento HTML almacenado en la constante 'button'
const clicks = Observable.fromEvent(button, "click");

// Creamos una constante llamada 'clicksSubscription' que suscribe una función de devolución de llamada que se ejecuta cada vez que se detecta un evento 'click'
const clicksSubscription = clicks.subscribe({
  // La función 'next' es llamada cada vez que se detecta un evento 'click' y muestra un mensaje de registro en la consola
  next(e) {
    console.log("Click in the button");
  },
});

Aunque hay diferencias en la implementación de la definición pura más simple de un observable entre el estándar antiguo y el nuevo, la invocación y ejecución del observable es muy similar en ambos casos.

❄️ Observables fríos ❄️

Los observables son "fríos" o "perezosos" por naturaleza, lo que significa que no hacen nada a menos que alguien los esté escuchando. Para desuscribirte de un observable, puedes usar el método dispose (estándar antiguo) o unsubscribe (estándar nuevo) que viene con la suscripción:

// Estandar antiguo
clicksSubscription.dispose();

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

🏁 Conclusiones 🏁

  1. La implementación de observables con ES7 es más corta y fácil de entender gracias al uso de class y constructor.
  2. En ES7, la función forEach se reemplaza por la función subscribe, y las funciones onNext, onComplete y onError se cambian a next, error y complete.
  3. Los observables son "fríos" o "perezosos" y solo se ejecutan cuando alguien los está escuchando. Para cancelar la suscripción, se utiliza el método dispose (estándar antiguo) o unsubscribe (estándar nuevo).

💡 Ejercicios propuestos 💡

  1. Crea un observable que muestre un mensaje cada vez que el usuario mueva el mouse sobre un elemento específico en la página.
  2. Implementa un observable que se ejecute cuando se presione una tecla en un campo de texto, y filtre solo los eventos de las teclas alfabéticas (a-z).
  3. Diseña un observable que combine eventos de múltiples elementos en la página y realice acciones en función de la secuencia de eventos.

🔝 Resumen en 3 puntos 🔝

  1. ES7 facilita la creación de observables gracias a la definición de clases y constructores, y la función subscribe reemplaza la función forEach.
  2. La función fromEvent nos permite crear observables a partir de eventos del DOM, facilitando la manipulación de eventos en aplicaciones web.
  3. Los observables son fríos y solo se ejecutan cuando tienen al menos un observador, lo que proporciona un mayor control sobre cuándo se ejecutan y cuándo se detienen.

Esperamos que este post te haya sido útil y te haya ayudado a comprender la naturaleza de los observables en JavaScript. ¡No dudes en dejar un comentario si tienes alguna pregunta, sugerencia o si deseas agregar alguna otra funcionalidad! Si te gustó, comparte este post en tus redes sociales. ¡Hasta la próxima! 🎉😄

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Related Posts

Categorias