Zademy

Curso Básico de RxJS con Vanilla JavaScript

RxJS; JavaScript; ProgramacionReactiva
words palabras

1. Introducción a la programación reactiva

La programación reactiva consiste en pensar en todo como flujos de datos que cambian con el tiempo.

Un click del mouse es un evento. Lo que escribes en un input es un evento. Una respuesta HTTP es un evento asíncrono.

En vez de andar preguntando "¿ya llegó algo?" cada cierto rato (polling), reaccionas cuando el dato llega. Es un cambio de mentalidad: de tirar (pull) a empujar (push).

2. Qué es RxJS

RxJS es una librería de JavaScript para componer programas asíncronos y basados en eventos usando Observables. La usan Angular, Redux Observable y media industria. Pero no necesitas Angular para aprenderla.

Instalación rápida

Con npm:

npm install rxjs

Con CDN:

<script src="https://unpkg.com/rxjs@7/dist/bundles/rxjs.umd.min.js"></script>

Estructura básica en Vanilla JS

Necesitas tres archivos y nada más: index.html, app.js y opcionalmente styles.css.

3. Observables

Un Observable es una fuente de datos que puede emitir 0, 1 o muchos valores a lo largo del tiempo. Piensa en ello como una manguera: tú la abres (subscribe) y los datos empiezan a salir.

const { of, from, interval } = rxjs;

of(1, 2, 3).subscribe(console.log); // 1, 2, 3
from(["A", "B"]).subscribe(console.log); // A, B

fromEvent

fromEvent convierte cualquier evento del DOM en un Observable. Esto es donde RxJS empieza a brillar en el navegador:

const { fromEvent } = rxjs;
const button = document.getElementById("btn");

const click$ = fromEvent(button, "click");
const sub = click$.subscribe(() => console.log("click"));

La convención de nombrar variables con $ al final viene del mundo reactivo. Significa "esto es un stream". Útil para distinguir de un valor normal.

Suscripción y desuscripción

Si te suscribes a un stream infinito y nunca te desuscribes, tienes un memory leak. Siempre limpia:

const { interval } = rxjs;

const sub = interval(1000).subscribe(v => console.log("tick", v));
setTimeout(() => sub.unsubscribe(), 5000);

4. Operadores básicos

Un operador transforma o filtra los valores que emite un Observable. Se encadenan con pipe. Esta es la parte donde RxJS deja de ser "otra librería más" y se convierte en una forma distinta de pensar:

const { of } = rxjs;
const { map, filter, take } = rxjs.operators;

of(1, 2, 3, 4, 5)
	.pipe(
		filter(n => n % 2 === 1),
		map(n => n * 10),
		take(2)
	)
	.subscribe(console.log); // 10, 30

Ese pipeline dice: "dame los impares, multiplícalos por 10, y para después de dos valores". El resultado es 10, 30. Limpio, declarativo, sin un solo if ni variable temporal.

Operadores que vas a usar siempre

  • Creación: of, from, interval, timer
  • Transformación: map, filter
  • Combinación: merge, concat
  • Filtrado: take, first, debounceTime

5. Manejo de eventos del DOM

Contador de clicks

const { fromEvent } = rxjs;

const btn = document.getElementById("btn");
const output = document.getElementById("output");
let count = 0;

fromEvent(btn, "click").subscribe(() => {
	count += 1;
	output.textContent = `Clicks: ${count}`;
});

Búsqueda en vivo (básica)

Aquí es donde debounceTime se vuelve tu mejor amigo. Sin él, cada tecla dispara una petición:

const { fromEvent } = rxjs;
const { map, debounceTime } = rxjs.operators;

const input = document.getElementById("search");

fromEvent(input, "input")
	.pipe(
		map(e => e.target.value.trim()),
		debounceTime(300)
	)
	.subscribe(term => console.log("Buscar:", term));

El usuario escribe "javascript" rápido (10 teclas en menos de un segundo). Sin debounce, disparas 10 peticiones. Con debounceTime(300), esperas 300ms de silencio y disparas una sola.

6. Manejo de asincronía

Observables vs Promesas

Una Promise entrega un valor y se termina. Un Observable puede entregar varios valores a lo largo del tiempo y puedes cancelarlo con unsubscribe. Una Promise no se puede cancelar. Esa es la diferencia práctica más importante.

Consumo de API con ajax de RxJS

const { ajax } = rxjs.ajax;

ajax
	.getJSON("https://jsonplaceholder.typicode.com/users")
	.subscribe({
		next: data => console.log("Usuarios:", data),
		error: err => console.error("Error HTTP:", err),
		complete: () => console.log("Completado")
	});

7. Manejo de errores y finalización

const { of } = rxjs;
const { catchError, retry } = rxjs.operators;
const { ajax } = rxjs.ajax;

ajax
	.getJSON("https://api.example.com/data")
	.pipe(
		retry(2),
		catchError(err => {
			console.error("Fallo:", err);
			return of([]);
		})
	)
	.subscribe({
		next: data => console.log(data),
		complete: () => console.log("Finalizado")
	});

retry(2) reintenta dos veces antes de rendirse. catchError captura el error y devuelve un Observable alternativo (en este caso, un array vacío). Así tu UI no explota si la API se cae.

8. Buenas prácticas

  • Limpia suscripciones en flujos infinitos (interval, fromEvent). Sin excepciones.
  • Encadena operadores con pipe para mantener legibilidad. Si tienes 8 operadores en un solo pipe, considera extraerlos.
  • No metas lógica pesada dentro de subscribe. El subscribe debería ser casi vacío.
  • Usa tap para debuggear cuando necesites inspeccionar valores sin modificar el flujo.

9. Proyecto final (propuesta)

Mini app de búsqueda en vivo con estos pasos:

  1. Input de texto.
  2. Escuchar con fromEvent.
  3. Aplicar map + debounceTime.
  4. Hacer petición HTTP con ajax.getJSON.
  5. Mostrar resultados en pantalla.
  6. Manejar error con catchError.

Con este proyecto practicas todo el curso en un solo flujo real. Si lo logras, ya entiendes RxJS suficiente para meterte a algo más serio.

10. Recursos y siguientes pasos

Documentación oficial:

Ejercicios recomendados:

  1. Cronómetro con interval.
  2. Contador con botones + y -.
  3. Búsqueda en vivo con API pública.
  4. Reintentos de petición con retry.

El siguiente paso natural es un curso intermedio: switchMap, mergeMap, Subjects y manejo avanzado de streams.