Angular 21 de Cero a Experto / RxJS y observables a gran escala
Errores y reintentos en RxJS
Los streams reales fallan: una petición se cae, la red parpadea, el servidor devuelve un 500.
En RxJS, un error TERMINA el Observable —el grifo se cierra— a menos que lo manejes. Hoy
aprendes a atrapar errores con catchError, a reintentar con retry, y a mantener tu stream
vivo pese a los fallos. La diferencia entre una app que se rompe con el primer error de red y
una que se recupera con elegancia.
El fusible que salta
Un error termina el stream
// si la petición falla, el Observable emite 'error' y COMPLETA (muere)
this.buscador.valueChanges.pipe(
switchMap((q) => this.api.buscar(q)), // si esto falla…
).subscribe({
next: (r) => this.mostrar(r),
error: (e) => console.error('el stream murió', e), // …llega aquí y NO vuelve a emitir
});
// tras el error, escribir en el buscador ya NO dispara búsquedas: el stream terminóEste es el error conceptual número uno con RxJS: un fallo dentro del pipe mata TODO el stream, no solo esa emisión. Por eso el manejo de errores va DENTRO del pipe, cerca de donde puede fallar.
catchError: atrapar y reemplazar
catchError intercepta el error y devuelve un Observable de reemplazo, evitando que el stream
muera:
import { catchError, of } from 'rxjs';
this.buscador.valueChanges.pipe(
switchMap((q) =>
this.api.buscar(q).pipe(
catchError((err) => {
console.error(err);
return of([]); // reemplaza el error por una lista vacía → el stream sigue vivo
}),
),
),
).subscribe((r) => this.mostrar(r));retry: intentar de nuevo
Para fallos transitorios (red inestable), retry vuelve a suscribirse al Observable fallido,
reintentando la operación:
import { retry, timer } from 'rxjs';
this.api.cargarGastos().pipe(
// reintenta hasta 3 veces, con espera creciente entre intentos (backoff)
retry({
count: 3,
delay: (error, intento) => timer(intento * 1000), // 1s, 2s, 3s
}),
catchError(() => of([])), // si tras 3 intentos sigue fallando, valor de reemplazo
).subscribe((gastos) => this.gastos.set(gastos));El delay con backoff (espera creciente) es la buena práctica: no bombardees un servidor que
ya está en problemas; dale tiempo entre reintentos. Y siempre combina retry con un
catchError final, por si todos los reintentos fallan.
Pruébalo: la lógica de reintento con backoff
La política de reintentos —cuántas veces, cuánto esperar, cuándo rendirse— es lógica pura. Aquí, un reintento con backoff simulando una operación inestable, en vivo:
Observa el patrón: intenta, y si falla, espera un tiempo CRECIENTE antes de reintentar, hasta
un máximo. Si se agotan los intentos, propaga el error final (donde catchError pondría el
valor de reemplazo). Es exactamente lo que hace retry({ count, delay }) de RxJS —lo has
construido con async/await para ver la mecánica—.
¿Por qué se coloca `catchError` DENTRO del `switchMap` (envolviendo la petición) y no en el pipe externo?
Mini-reto
En el playground: 1) cambia operacionInestable para que falle SIEMPRE y verifica que tras
agotar los intentos se lanza el error final; 2) añade un backoff exponencial (2 ** intento * baseMs
en vez de lineal) y observa las esperas; 3) piensa cómo escribirías esto en RxJS con
retry({ count, delay }) + catchError(() => of(valorPorDefecto)).
Qué sigue
Dominas RxJS: streams, operadores, aplanamiento, combinación y errores. Ahora la pregunta que
define Angular moderno: si ya tienes señales para el estado, ¿cuándo usas RxJS y cuándo
señales? Y ¿cómo conectas ambos mundos? La próxima lección es la interop
toSignal/toObservable —el puente entre los dos sistemas reactivos— y la guía definitiva de
cuándo usar cada uno.