RxJS: 1. Introducción

Primera entrada de una serie dedicada a RxJS: Introducción a los streams, observables y observers.

Vangel Naumovski

archivado en: JavaScript / 8 enero, 2018 / taller:

Qué es la programación reactiva

Impulsada por los grandes frameworks js, cada vez está adquiriendo mayor importancia la denominada programación reactiva (Reactive Programming), una nueva manera de hacer las cosas que puede producir cierto desconcierto en un primer momento, al menos en el mundo front más clásico, pero sobre la que vale la pena profundizar. En esencia, la programación reactiva busca tejer una red de datos que pueden cambiar a lo largo del tiempo y en cosas que reaccionan de la manera más transparente posible cuando cambia el valor de esos datos como resultado de algún evento, como el click de un ratón o el desenlace de una llamada ajax.

Para entenderlo, podemos pensar en una red de tuberías por las que van corriendo los datos a lo largo del tiempo y en piezas de una maquinaria que se alimentan de esos datos. Estas secuencias o colecciones de valores se conocen como streams. Por ejemplo, un stream podría estar formado por un array con el nombre de grandes escritores:

let myStream = ['homero', 'shakespeare', 'proust'];

pero también podría ser un json con sus obras:

let myStream = [

{ author: 'homero', books: ['La Iliada', 'La Odisea'] }

{ author: 'shakespeare', books: ['El Mercader...', 'Sueño..'] }

{ author: 'proust', books: ['En busca..'] }

];

O incluso por un mero string con el nombre de un solo escritor:

let myStream =  'homero';

El ideal de la programación reactiva en front sería, por lo tanto, que toda nuestra aplicación estuviera formada por piezas, por componentes, que fueran reaccionando de una manera u otra según fueran cambiando los datos que corren por estos streams, por las tuberías, a lo largo del tiempo, por lo general, como resultado de la interacción del usuario. Por ejemplo, imaginemos un mensaje de alerta que debe mostrarse cuando un usuario escribe un campo mal en un formulario. Si lo hiciéramos de forma tradicional, tendríamos que preparar algo así:

  1. En el submit del formulario, recoger el valor del input:
  2. Comprobamos que siga las reglas que debe cumplir, como contener una arroba.
  3. Si está equivocado, debemos insertar en el dom un div con el mensaje de error.

De forma reactiva, sin embargo, el proceso sería algo así:

  1. Tenemos un stream con los valores del formulario.
  2. Tenemos un segundo stream relacionado con el anterior con dos valores posibles, false o true, que van cambiando de forma automática (o mejor dicho, transparente para el programador) según el input cumpla o no las reglas.
  3. Una parte de la vista, como un componente mensaje, reacciona ante el valor que tenga el segundo stream en cada momento mostrándose o no.

Hay varias librerías open source para trabajar siguiendo los patrones de la programación reactiva, como bacon.js o, más compleja, Reactive Extensions (RX), que en su versión para javaScript se llama RxJS y será la que trataré en esta serie.

El sustrato teórico esencial de RX es el denominado patrón observer.

Introducción a los observables

Los dos actores principales de RX son los observables y los observers. En esencia, un observable es un stream al que podemos suscribir observers que reaccionan cuando se emiten datos. Aunque es una analogía peligrosa, podemos pensar que los primeros equivalen a un emisor de eventos y el segundo a un receptor de eventos.

Esto en código queda más claro, pero para verlo en acción antes debemos instalar la librería. La forma canónica sería mediante yarn o npm:

npm install @reactivex/rxjs

Y luego importar las partes que se vayan a necesitar en cada pieza de nuestra aplicación, como un servicio o componente de @angular.

import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/of';

...

Pero para esta introducción vamos a seguir un procedimiento más sencillo y es enlazar la librería en el index.html de una página normal.

<head>
...
<script src="https://unpkg.com/rxjs@5.5.5/bundles/Rx.min.js"></script>

</head>

Luego enlazamos también un archivo js donde iremos poniendo los snippet de código.

<script src="./first.js"></script>

Se pueden crear observables  de varias formas, pero en esta entrada solo vamos a ver una y es mediante el método create(), el cual recibe una función callback  en la que definimos lo que debe hacer el observable. Por claridad, además, vamos a cachearlo en una variable.

let myObservable = Rx.Observable.create( (observer)=> {

observer.next('foo');

});

Ahora ya podemos suscribir observers a este observable mediante el método suscribe() y estos observers recibirán ese foo que se está enviando con el método next que veremos dentro de un momento.

let myObserver = myObservable.subscribe(

(msg)=> {

console.log(`Observable resuelto ${msg}`);

}

);

// Observable resuelto foo

Qué devuelve un observer

Una función solo devuelve un valor, que es el definido en el return (incluso cuando no se retorna aparentemente nada, se está retornando undefined).

function foo() {

return 'foo';

/* Nunca se llega a este segundo return; la función muere en el primero */

return 'bar';

}

Con ecmaScript 6, sin embargo, llegó una manera nueva de crear funciones que permiten devolver más de un valor. Son las llamadas funciones generadoras y se escriben añadiendo un asterisco al término function. En una función de este tipo cada valor se devuelve mediante la expresión yield, que se podría traducir como ofrecido, dado. Para recoger esos valores se invoca al método next().

function* demoGenerator() {

yield 1;

yield 2;

}

let myDemoGenerator = demoGenerator();

console.log(myDemoGenerator.next().value); // 1

console.log(myDemoGenerator.next().value); // 2

Los observables funcionan de manera similar a las funciones generadoras, pero añadiendo los conceptos de onError y onComplete de las promesas clásicas de javaScript, de tal manera que el ciclo de vida de un observable estaría formado por tres grandes grupos de métodos.

  1. next([value]): Como ocurre con la expresión yield, se pueden lanzar n next, los cuales devuelven un valor.
  2. error([error]): Se lanza cuando sucede un error.
  3. complete(): No devuelve nada y se lanza cuando ha terminado la secuencia de next().

Ojo cuando se lea la documentación general que se puede encontrar por Internet, que en las versiones previas a la 5, estos métodos se denominaban onNext() onComplete() y onError().

Estos métodos se recogen en el observer en tres funciones: en la primera los n next, en la segunda el error, si es que se produce, y en la tercera el complete, que puede darse o no según sea el stream finito (como un array) o infinito (como un click de ratón). En código queda más claro:

let observableLifecycle = Rx.Observable.create(

(observer)=> {

try {

observer.next(1);

observer.next(2);

} catch(error) {

observer.error(error);

}

observer.complete();

}

);

let myObserverLifecycle = observableLifecycle.subscribe(

(msg)=> {

console.log(`Observable recibido: ${msg}`);

},

(error)=> {

console.log(`Algo ha salido mal: ${error}`);

},

(msg)=> {

console.log(`Observable terminado`);

}

);

Lo que daría por consola estos mensajes:

Observable recibido: 1

Observable recibido: 2

Observable terminado

En cambio, si introducimos un error, no se lanzaría el complete, sino el método que recoge error.

...

observer.next(2);

/* provocamos un error */

zaaas

} catch(error) {

observer.error(error);

}

...

Observable recibido: 1

Observable recibido: 2

Algo ha salido mal: ReferenceError: zaaas is not defined

Un observer no emite ningún valor hasta que no recibe la suscripción de un observer.

Desuscribir observers

Al igual que podemos eliminar una escucha de un evento con removeEventListener, podemos desuscribir un observer de un observable con su método unsuscribe().

myObservable.subscribe((value) => {

...

});

/* Desuscribimos el observer */

myObserver.unsubscribe();

Además, podemos indicar que se ejecute algo cuando se produce una desuscripción retornando una función al final del observable. Así, por ejemplo, podríamos eliminar definitivamente el interval interno y no dejarnos basurillas por ahí.

/* Este observable emite el valor incremental de i cada 1000 milisegundos */

myObservable = new Rx.Observable.create((observer) => {

let i = 0;

let timer = setInterval(() => {

observer.next(i++);

}, 1000);

/* Podemos aprovechar la llamada final que se produce al desuscribir un observer para hacer algo, como limpiar los interval y los timeoout */

return function unsubscribe() {

clearInterval(timer);

}

});

myObserver = myObservable.subscribe((value) => {

console.log(`Observable recibido: ${value}`);

});

/* A los 3000 milisegundos desuscribimos el observer */

setTimeout(() => {

myObserver.unsubscribe();

}, 3000);

// Observable recibido: 1

// Observable recibido: 2

Diagramas de canicas

Los observers y sus posibles operadores, que veremos en la siguiente entrada, se suelen representar con los llamados marble diagrams, diagramas de canicas, que son una sucesión de círculos en una línea que termina en forma de flecha. Cada círculo representa un dato, una x o similar el error y una línea horizontal el complete.

Por ejemplo, este observer:

Rx.Observable.create(

(observer)=> {

try {

observer.next(1);

observer.next(2);

observer.next(3);

observer.next(4);

} catch(error) {

observer.error(error);

}

observer.complete();

}

);

se podría representar mediante este diagrama:

También se suelen representar mediante código ASCII:

--1--2--3--4--X---|->

El lodash de los eventos

La primera vez que uno se sumerge en los observables de RxJS se queda un tanto desconcertado o al menos fue lo que me sucedió a mí. ¿Qué son exactamente entonces? ¿Una manera de organizar la información? ¿Un sistema de promesas? ¿Un nuevo tipo de funciones? La respuesta no es ninguna de las anteriores.

RxJS es una librería para trabajar de forma reactiva con eventos, que, recordemos, en javaScript son:

  • Eventos del ratón, como un click.
  • Eventos del teclado.
  • Eventos relacionados con la carga de datos, como el DOMContentLoaded.
  • Los relacionados con las animaciones.
  • Eventos relacionados con el procesamiento de archivos mediante ajax.
  • Los propios de los web sockets.
  • Los eventos de los web workers.

De ahí que se diga que RxJS es el equivalente a la librería lodash, pero para eventos, ya sean estos síncronos o asíncronos, y los observables son, en suma, como funciones con cero argumentos que, a diferencia de estas, pueden devolver múltiples valores.

En fin, no sé si esta primera entrada servirá para despertar el interés por los stream y los observers o si nos estaremos preguntando si realmente valen para algo teniendo ya el sistema formidable de gestión de eventos de javaScript, incluidas las promesas (que solo devuelven un valor, no varios); pero animo a los más recelosos a suspender el juicio hasta no haber ojeado las dos siguientes entradas de esta serie, en las que hablaré de los operadores, que permiten ya sí vislumbrar el potencial de la librería.

 

Los ejemplos anteriores se encuentran en git hub.

|| Tags: , ,

valoración de los lectores sobre RxJS: 1. Introducción

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 5 sobre 5 (6 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

Los comentarios están cerrados.