RxJs: Introducción a las extensiones reactivas

RxJs: Introducción a las extensiones reactivas

Tabla de Contenido

En aplicaciones web modernas, la programación reactiva aparece cuando el sistema debe responder a múltiples fuentes de eventos sin bloquear la interfaz: entradas del usuario, respuestas HTTP, mensajes por WebSocket, timers, estado compartido y cambios que llegan en momentos distintos. Administrar esos flujos solo con callbacks o promesas aisladas suele aumentar el acoplamiento y dificulta expresar transformaciones, cancelaciones y composición de eventos. RxJs aborda este problema con extensiones reactivas basadas en flujos de datos observables.

Note

Este artículo abre la serie RxJs. Analizaremos qué son las extensiones reactivas, qué papel cumple ReactiveX, cómo se relacionan Observables, Subscribers y Operators, cómo leer diagramas de canicas y cómo preparar el proyecto base. No hay artículo anterior; el siguiente artículo es Observables.

Por qué usar extensiones reactivas en aplicaciones web

La expectativa actual de una interfaz es operar en tiempo real o casi real. Un formulario con autocompletado, un dashboard con métricas en vivo o una pantalla de trading no puede depender de recargas completas de página para reflejar cambios. La aplicación necesita reaccionar a eventos concurrentes y mantener la experiencia de usuario estable mientras ejecuta trabajo asíncrono.

Las extensiones reactivas permiten modelar esos eventos como flujos. En lugar de distribuir lógica en múltiples callbacks, representamos la fuente de datos como un Observable y aplicamos operadores para transformar, filtrar, combinar o controlar la frecuencia de sus emisiones.

Esta aproximación es útil cuando una interacción puede producir muchos valores a lo largo del tiempo. Una promesa representa un resultado único; un observable puede representar cero, uno, muchos o infinitos valores, además de errores y finalización.

import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';

const searchInput = document.querySelector<HTMLInputElement>('#search')!;

const searchTerm$ = fromEvent<InputEvent>(searchInput, 'input').pipe(
  debounceTime(300),
  map((event) => (event.target as HTMLInputElement).value.trim())
);

searchTerm$.subscribe((term) => {
  console.log('Buscar:', term);
});

El sufijo $ en searchTerm$ es una convención frecuente para indicar que la variable representa un flujo observable. No es obligatorio, pero mejora la legibilidad cuando conviven valores normales y streams.

Tip

Usa RxJs cuando necesites componer eventos en el tiempo: entradas de usuario, sockets, polling, estado reactivo, cancelaciones, reintentos y coordinación entre múltiples fuentes asíncronas.

Cuándo usar programación reactiva con RxJs

RxJs aporta más valor cuando el problema se expresa naturalmente como una secuencia de emisiones. No todos los casos asíncronos requieren un Observable; una petición HTTP aislada puede resolverse con Promise sin introducir más abstracción. El punto fuerte de RxJs aparece cuando necesitas componer varias señales y mantener control explícito sobre su ciclo de vida.

Casos comunes de uso:

  • Eventos de interfaz de usuario: clicks, movimientos del mouse, inputs, scroll o drag and drop.
  • Notificación de cambios en objetos o estado compartido: propagación de cambios hacia múltiples consumidores.
  • Comunicación por sockets: mensajes que llegan indefinidamente y deben procesarse con filtros o transformaciones.
  • Flujos de información: secuencias que pueden combinar datos de red, timers, cache y eventos locales.
  • Control de concurrencia: cancelación de peticiones obsoletas, serialización de operaciones o limitación de frecuencia.

Consideremos un autocompletado. Cada tecla puede disparar una solicitud de red, pero no todas las solicitudes siguen siendo relevantes. RxJs permite aplicar debounceTime, eliminar valores repetidos con distinctUntilChanged y cancelar peticiones previas con switchMap.

import { fromEvent, switchMap } from 'rxjs';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';

const query$ = fromEvent<InputEvent>(searchInput, 'input').pipe(
  map((event) => (event.target as HTMLInputElement).value.trim()),
  debounceTime(300),
  distinctUntilChanged(),
  switchMap((query) => fetch(`/api/search?q=${encodeURIComponent(query)}`))
);

La implementación expresa la intención del flujo: capturar entrada, esperar estabilidad, evitar duplicados y ejecutar la consulta vigente. La lógica queda declarativa y evita coordinación manual con banderas de estado mutable.

Elementos de las extensiones reactivas

RxJs organiza su modelo alrededor de tres piezas principales: Observables, Subscribers y Operators. Entender la responsabilidad de cada una evita mezclar producción de datos, consumo y transformación en el mismo bloque de código.

Observables: la fuente de información

Un Observable representa una fuente de valores a lo largo del tiempo. Puede emitir múltiples valores, un único valor o ninguno. También puede emitir un error o completar su ciclo de vida.

Sus propiedades principales son:

  • Puede ser finito o infinito.
  • Puede emitir de forma síncrona o asíncrona.
  • Puede notificar valores, errores y finalización.
  • Puede encapsular detalles internos como eventos DOM, sockets, timers o APIs.
import { Observable } from 'rxjs';

const counter$ = new Observable<number>((subscriber) => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

En este ejemplo, counter$ define la fuente de emisiones. La ejecución real ocurre cuando alguien se suscribe; antes de eso, solo existe la definición del flujo.

Subscribers: consumidores del flujo

Un Subscriber consume las emisiones de un observable. Puede reaccionar a valores (next), errores (error) y finalización (complete). La suscripción también representa un recurso que debe liberarse cuando el flujo deja de ser necesario, especialmente en observables infinitos.

const subscription = counter$.subscribe({
  next: (value) => console.log('Valor:', value),
  error: (error) => console.error('Error:', error),
  complete: () => console.log('Flujo completado'),
});

subscription.unsubscribe();

El consumidor no necesita conocer si los datos provienen de un arreglo, un WebSocket, un evento DOM o una API. Esa separación reduce el acoplamiento entre la fuente de datos y quienes procesan sus emisiones.

Operators: transformación, filtrado y composición

Los Operators son funciones que permiten trabajar con observables sin mutar directamente la fuente original. Se encadenan mediante pipe() y habilitan operaciones de transformación, filtrado, combinación y creación.

Algunos operadores frecuentes son:

  • Transformación: map, scan, groupBy.
  • Filtrado: filter, distinctUntilChanged, skip, debounceTime.
  • Combinación: merge, concat, combineLatest, zip.
  • Creación: of, from, interval, timer.
import { of } from 'rxjs';
import { filter, map, scan } from 'rxjs/operators';

of(1, 2, 3, 4, 5)
  .pipe(
    filter((value) => value % 2 === 1),
    map((value) => value * 10),
    scan((total, value) => total + value, 0)
  )
  .subscribe((value) => console.log(value));

Aquí el flujo filtra impares, transforma cada emisión y acumula el resultado. La fuente original no se modifica; cada operador devuelve un nuevo observable derivado.

Info

La composición con operadores es una de las razones principales para adoptar RxJs: permite expresar pipelines asíncronos complejos con una semántica consistente para valores, errores y finalización.

Beneficios de RxJs y las extensiones reactivas

El beneficio más visible de RxJs es evitar estructuras difíciles de mantener como el callback hell. Sin embargo, su valor no se limita a reemplazar callbacks. RxJs ofrece un modelo uniforme para tratar trabajo síncrono y asíncrono como flujos componibles.

Entre sus ventajas principales están:

  1. Reduce el anidamiento de callbacks y centraliza la lógica del flujo.
  2. Permite trabajar con tareas síncronas y asíncronas bajo la misma abstracción.
  3. Usa operadores para simplificar transformación, filtrado y composición.
  4. Facilita modificar streams sin alterar el consumidor final.
  5. Mejora la legibilidad cuando el problema depende de eventos en el tiempo.
  6. Permite anexar pasos al pipeline sin reescribir toda la implementación.

Esta capacidad de composición es especialmente relevante en aplicaciones grandes. Puedes aislar responsabilidades en operadores específicos y mantener flujos más predecibles, en lugar de dispersar estado mutable entre handlers independientes.

Warning

RxJs no elimina la complejidad del dominio. La desplaza hacia un modelo explícito de streams. Si el problema no involucra secuencias, cancelación, coordinación o múltiples emisiones, una abstracción más simple puede ser suficiente.

Qué es ReactiveX

ReactiveX es una API para programación asíncrona basada en observables. Su objetivo es proporcionar una forma consistente de componer secuencias de eventos y datos a través de operadores. RxJs es la implementación de ReactiveX para JavaScript y TypeScript, documentada oficialmente en rxjs.dev.

ReactiveX se apoya en tres ideas fundamentales: el patrón Observer, el patrón Iterator y la programación funcional. La combinación de estas ideas explica por qué RxJs puede representar datos que llegan en el tiempo, recorrer secuencias y aplicar transformaciones sin mutar la fuente.

Patrón Observer: notificación uno a muchos

El patrón Observer define una relación uno a muchos entre objetos. Cuando el sujeto cambia su estado, notifica a todos sus observadores. En RxJs, el observable cumple el rol de fuente y cada suscripción representa un consumidor interesado en sus emisiones.

flowchart LR
    S[Observable / Subject] -->|next value| A[Subscriber A]
    S -->|next value| B[Subscriber B]
    S -->|next value| C[Subscriber C]
    S -->|error / complete| A
    S -->|error / complete| B
    S -->|error / complete| C

Este patrón resulta natural para interfaces, sockets y estado compartido. La fuente emite cambios y los consumidores reaccionan sin conocer los detalles internos de producción.

Patrón Iterator: operaciones secuenciales

El patrón Iterator define una forma de acceder secuencialmente a los elementos de una colección. ReactiveX toma esa idea y la proyecta sobre el tiempo: en lugar de recorrer una colección ya disponible, procesa valores conforme se emiten.

Un arreglo entrega valores inmediatamente. Un observable puede entregar valores ahora, más tarde o nunca completar. Esa diferencia temporal es la razón por la que los operadores de RxJs son tan útiles para coordinar flujos asíncronos.

Programación funcional: composición sin mutación

La programación funcional aporta la idea de construir transformaciones con funciones específicas, predecibles y sin efectos secundarios innecesarios. Operadores como map y filter no mutan la fuente; generan nuevos observables derivados.

Esta disciplina reduce el acoplamiento y facilita razonar sobre el flujo. Cada operador cumple una responsabilidad concreta y el pipeline completo documenta el recorrido de los datos.

Diagrama de canicas en RxJs

Los diagramas de canicas son una convención visual para explicar cómo se comportan los observables a lo largo del tiempo. Cada línea representa un stream, cada canica representa una emisión, las barras verticales indican finalización y los operadores muestran cómo se transforma el flujo.

Imagen guia del contenido de la página

Este tipo de diagrama es especialmente útil para entender operadores temporales como debounceTime, throttleTime, delay, switchMap, mergeMap o concatMap. En lugar de describir cada emisión con texto, el diagrama permite comparar el flujo de entrada, el operador aplicado y el flujo resultante.

Al leer un diagrama de canicas, considera estos puntos:

  • El tiempo avanza de izquierda a derecha.
  • Cada símbolo o canica representa una emisión del observable.
  • La barra vertical representa complete.
  • Una X suele representar error.
  • Los operadores transforman la relación entre entrada y salida.

Tip

Cuando estudies un operador nuevo, revisa primero su diagrama de canicas y después valida el comportamiento con código. Esa secuencia reduce ambigüedad en operadores temporales y de concurrencia.

Configuración del proyecto para la serie RxJs

Para seguir la serie usaremos como base el repositorio del curso de Klerith: Klerith/curso-rxjs-inicio. Este proyecto proporciona una estructura inicial para practicar RxJs sin dedicar tiempo a configurar desde cero el entorno.

Puedes clonar el repositorio y preparar las dependencias con los comandos habituales:

git clone https://github.com/Klerith/curso-rxjs-inicio.git
cd curso-rxjs-inicio
npm install
npm start

Si prefieres crear un proyecto propio, instala RxJs desde npm y configura TypeScript según tu stack:

npm install rxjs

El objetivo de esta serie no es cubrir la configuración de bundlers, sino entender el modelo mental de RxJs y sus operadores. Mantener un proyecto pequeño al inicio permite enfocarse en observables, suscripciones, composición y manejo del ciclo de vida.

Conclusión

RxJs permite modelar eventos y datos asíncronos como flujos observables. Esta abstracción es útil cuando la aplicación necesita reaccionar a múltiples emisiones, coordinar fuentes, transformar datos y controlar el ciclo de vida de suscripciones.

ReactiveX combina Observer, Iterator y programación funcional para construir una API coherente alrededor de streams. En el siguiente artículo profundizaremos en Observables: cómo se crean, cuándo ejecutan, qué implica suscribirse y cómo gestionar su finalización correctamente.

Etiquetas :