Curso Básico de RxJS con Vanilla JavaScript
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 rxjsCon 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, BfromEvent
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, 30Ese 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
pipepara 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
tappara debuggear cuando necesites inspeccionar valores sin modificar el flujo.
9. Proyecto final (propuesta)
Mini app de búsqueda en vivo con estos pasos:
- Input de texto.
- Escuchar con
fromEvent. - Aplicar
map+debounceTime. - Hacer petición HTTP con
ajax.getJSON. - Mostrar resultados en pantalla.
- 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:
- Cronómetro con
interval. - Contador con botones
+y-. - Búsqueda en vivo con API pública.
- Reintentos de petición con
retry.
El siguiente paso natural es un curso intermedio: switchMap, mergeMap, Subjects y manejo avanzado de streams.