Parte 1. Reactividad y flujos
Esta serie de artículos se centra en la reactividad y su aplicación en JS utilizando una biblioteca tan maravillosa como RxJS.
Serie de artículos "Fundamentos de la programación reactiva con RxJS":
Para quién es este artículo : básicamente, aquí explicaré los conceptos básicos, por lo tanto, el artículo está destinado principalmente a principiantes en esta tecnología. Al mismo tiempo, espero que los desarrolladores experimentados puedan aprender algo nuevo por sí mismos. La comprensión requerirá conocimiento de js (es5 / es6).
Motivación : Primero encontré RxJS cuando comencé a trabajar con angular. Fue entonces cuando tuve dificultades para comprender el mecanismo de reactividad. El hecho de que en el momento del inicio de mi trabajo la mayoría de los artículos se dedicaba a la versión antigua de la biblioteca se sumó a las dificultades. Tuve que leer mucha documentación, varios manuales para al menos entender algo. Y solo después de un tiempo comencé a darme cuenta de cómo "todo está organizado". Para facilitarle la vida a los demás, decidí poner todo en los estantes.
¿Qué es la reactividad?
Es difícil encontrar una respuesta a un término aparentemente común. En resumen: la reactividad es la capacidad de responder a cualquier cambio. ¿Pero de qué cambios estamos hablando? En primer lugar, sobre los cambios de datos. Considere un ejemplo:
let a = 2; let b = 3; let sum = a + b; console.log(sum);
Este ejemplo demuestra el conocido paradigma de programación imperativa. A diferencia del enfoque imperativo, el enfoque reactivo se basa en estrategias de propagación de cambio de empuje. La estrategia de inserción implica que, en caso de cambios en los datos, estos mismos cambios serán "introducidos" y los datos que dependen de ellos se actualizarán automáticamente. Así es como se comportaría nuestro ejemplo si se aplicara una estrategia de inserción:
let a = 2; let b = 3; let sum = a + b; console.log(sum);
Este ejemplo muestra un enfoque reactivo. Vale la pena señalar que este ejemplo no tiene nada que ver con la realidad; lo di solo para mostrar la diferencia en los enfoques. El código reactivo en las aplicaciones del mundo real se verá muy diferente, y antes de pasar a la práctica, deberíamos hablar sobre otro componente importante de la reactividad.
Flujo de datos
Si observa el término "programación reactiva" en Wikipedia, el sitio nos dará la siguiente definición: "La programación reactiva es un paradigma de programación centrado en los flujos de datos y la propagación de cambios". A partir de esta definición, podemos concluir que la reactividad se basa en dos "ballenas" principales. Mencioné la distribución de cambios arriba, así que no nos detendremos más en esto. Pero deberíamos hablar más sobre los flujos de datos. Veamos el siguiente ejemplo:
const input = document.querySelector('input');
Escuchamos el evento keyup y colocamos el objeto de evento en nuestra matriz. Con el tiempo, nuestra matriz puede contener miles de objetos KeyboardEvent. Vale la pena señalar que nuestra matriz está ordenada por tiempo: el índice de eventos posteriores es mayor que el índice de eventos anteriores. Tal matriz es un modelo de flujo de datos simplificado. ¿Por qué simplificado? Porque la matriz solo puede almacenar datos. También podemos iterar la matriz y de alguna manera procesar sus elementos. Pero la matriz no puede decirnos que se le ha agregado un nuevo elemento. Para saber si se han agregado nuevos datos a la matriz, tendremos que repetirlos nuevamente.
Pero, ¿qué pasaría si nuestra matriz supiera cómo informarnos que llegaron nuevos datos? Tal conjunto podría llamarse con certeza una secuencia. Entonces, llegamos a la definición de flujo. Una secuencia es una matriz de datos ordenados por tiempo que puede indicar que los datos han cambiado.
Observable
Ahora que sabemos qué son los flujos, trabajemos con ellos. En RxJS, los flujos están representados por la clase Observable. Para crear su propia secuencia, simplemente llame al constructor de esta clase y pase la función de suscripción como argumento:
const observable = new Observable(observer => { observer.next(1); observer.next(2); observer.complete(); })
Al llamar al constructor de la clase Observable, creamos un nuevo hilo. Como argumento, pasamos la función de suscripción al constructor. La función de suscripción es una función regular que toma un observador como parámetro. El observador mismo es un objeto que tiene tres métodos:
- siguiente: arroja un nuevo valor a la transmisión
- error: arroja un error en la secuencia, después de lo cual la secuencia termina
- complete - termina el hilo
Entonces creamos un hilo que emite dos valores y termina.
Suscripción
Si ejecutamos el código anterior, no pasará nada. Solo crearemos una nueva secuencia y guardaremos el enlace en la variable observable, pero la secuencia en sí misma nunca emitirá un solo valor. Esto se debe a que los hilos son objetos "vagos" y no hacen nada por sí mismos. Para que nuestra transmisión comience a emitir valores y podamos procesar estos valores, debemos comenzar a "escuchar" la transmisión. Puede hacer esto llamando al método de suscripción en el objeto observable.
const observer = { next: value => console.log(value),
Identificamos a nuestro observador y le describimos tres métodos: a continuación, error, completo. Los métodos simplemente registran los datos que se pasan como parámetros. Luego llamamos al método de suscripción y le pasamos nuestro observador. En el momento de llamar a subscribe, se llama a la función de suscripción, la que pasamos al constructor en la etapa de declarar nuestra secuencia. A continuación, se ejecutará el código de la función de suscripción, que pasa dos valores a nuestro observador y luego finaliza la transmisión.
Seguramente, muchos tienen una pregunta, ¿qué sucederá si nos suscribimos a la transmisión nuevamente? Todo será igual: la secuencia volverá a pasar dos valores al observador y al final. Cada vez que se llama al método de suscripción, se llamará a una función de suscripción y se volverá a ejecutar todo su código. De esto podemos concluir: no importa cuántas veces nos suscribamos a la secuencia, nuestros observadores recibirán los mismos datos.
Darse de baja
Ahora intentemos implementar un ejemplo más complejo. Escribiremos un temporizador que contará segundos desde el momento de la suscripción y los transmitirá a los observadores.
const timer = new Observable(observer => { let counter = 0;
El código es bastante simple. Dentro de la función de suscripción, declaramos una variable de contador. Luego, usando el cierre, accedemos a la variable desde la función de flecha en setInterval. Y cada segundo pasamos la variable al observador, después de lo cual la incrementamos. A continuación, suscríbase a la secuencia, especifique solo un método: el siguiente. No se preocupe que no hayamos anunciado otros métodos. No se requiere ninguno de los métodos de observación. Incluso podemos pasar un objeto vacío, pero en este caso el hilo se desperdiciará.
Después del lanzamiento, veremos los codiciados registros que aparecerán cada segundo. Si lo desea, puede experimentar y suscribirse a la transmisión varias veces. Verá que cada uno de los hilos se ejecutará independientemente de los demás.
Si lo piensa, nuestro hilo se ejecutará durante toda la vida de la aplicación, porque no tenemos ninguna lógica para cancelar setInterval, y en la función de suscripción no hay llamada al método completo. Pero, ¿y si necesitamos que el hilo termine?
De hecho, todo es muy simple. Si mira la documentación, puede ver que el método de suscripción devuelve un objeto de suscripción. Este objeto tiene un método para darse de baja. Lo llamamos y nuestro observador dejará de recibir valores de la secuencia.
const subscription = timer.subscribe({next: console.log}); setTimeout(() => subscription.unsubscribe(), 5000);
Después de comenzar, veremos que el contador se detiene en 4. Pero, aunque nos hemos dado de baja de la secuencia, nuestra función setInterval continúa funcionando. Ella incrementa nuestro contador cada segundo y se lo pasa al observador ficticio. Para evitar que esto suceda, debe escribir la lógica para cancelar el intervalo. Para hacer esto, debe devolver una nueva función de la función de suscripción en la que se implementará la lógica de cancelación.
const timer = new Observable(observer => { let counter = 0; const intervalId = setInterval(() => { observer.next(counter++); }, 1000); return () => { clearInterval(intervalId); } });
Ahora podemos respirar aliviados. Después de llamar al método de cancelación de suscripción, se llamará a nuestra función de cancelación de suscripción, que borrará el intervalo.
Conclusión
Este artículo muestra las diferencias entre los enfoques imperativo y reactivo, así como ejemplos de cómo crear sus propios flujos. En la siguiente parte, hablaré sobre qué otros métodos existen para crear hilos y cómo aplicarlos.