🌊 Programación Reactiva Cheatsheet Completo 🌊
La Programación Reactiva es un paradigma de programación que se centra en el trabajo con flujos de datos asíncronos y propagación de cambios. Trata todo como un “stream” (flujo) de eventos o datos que pueden ser observados, transformados y combinados.
1. 🌟 Principios Fundamentales (El Manifiesto Reactivo)
La programación reactiva se adhiere a los principios del Manifiesto Reactivo, que promueve sistemas que son:
- Responsivos (Responsive): El sistema responde de manera oportuna si es posible.
- Resilientes (Resilient): El sistema permanece responsivo frente a fallas.
- Elásticos (Elastic): El sistema permanece responsivo bajo cargas de trabajo variables.
- Orientados a Mensajes (Message-Driven): Los componentes se comunican entre sí de forma asíncrona mediante el paso de mensajes.
2. 🧩 Componentes Clave (RxJS / Project Reactor Terminología)
Aunque los nombres pueden variar ligeramente entre librerías (ej. RxJava, RxJS, Project Reactor, Kotlin Flows), los conceptos son los mismos:
- Observable / Publisher (Productor del Flujo):
- Representa un flujo de datos o eventos que se emiten a lo largo del tiempo.
- Puede emitir tres tipos de notificaciones a sus suscriptores:
next/onNext: Emite un nuevo valor o dato.error/onError: Emite un error, lo que detiene el flujo.complete/onComplete: Emite una señal de finalización, lo que detiene el flujo.
- Los Observables son “lazies”; no hacen nada hasta que alguien se suscribe a ellos.
- Observer / Subscriber (Consumidor del Flujo):
- Es la entidad que “escucha” o “reacciona” a las notificaciones emitidas por un Observable.
- Define funciones de callback para manejar cada tipo de notificación (
next,error,complete).
- Subscription / Disposable (Suscripción):
- El resultado de una suscripción a un Observable. Representa el ciclo de vida de la ejecución del Observable.
- Permite
unsubscribe()/dispose()/cancel()para detener el consumo del flujo y liberar recursos.
- Operators (Operadores):
- Funciones puras que transforman, filtran, combinan o manipulan Observables, devolviendo siempre un nuevo Observable.
- Permiten construir tuberías de procesamiento de datos complejas de forma declarativa.
- Scheduler / Context (Programador / Contexto de Ejecución):
- Determina dónde y cuándo se ejecuta el trabajo en un flujo reactivo (ej. en qué hilo, de forma síncrona o asíncrona).
- Crucial para gestionar la concurrencia y paralelismo.
- Backpressure (Contrapresión):
- Un mecanismo para gestionar el flujo de datos entre un productor rápido y un consumidor lento, evitando que el consumidor se sature o se quede sin memoria.
- El consumidor puede indicarle al productor que reduzca la velocidad de emisión o que le envíe menos elementos.
3. 🎯 Operadores Comunes (RxJS / Reactor Pseudocódigo)
Los operadores son el corazón de la programación reactiva. Se encadenan utilizando el método pipe() (RxJS) o .map().filter()... (Reactor).
3.1. Operadores de Creación (Crear Observables)
of(values...): Emite valores proporcionados en secuencia y luego completa.// RxJS of(1, 2, 'hello').subscribe(...) // Emite 1, 2, 'hello'from(iterable): Convierte un iterable (array, promesa, etc.) en un Observable.// RxJS from([10, 20, 30]).subscribe(...) // Emite 10, 20, 30interval(ms): Emite un número secuencial cadamsmilisegundos indefinidamente.// RxJS interval(1000).subscribe(...) // Emite 0, 1, 2... cada segundotimer(initialDelay, period): Emite un valor después deinitialDelay, y luego valores secuenciales cadaperiodmilisegundos.empty(): Crea un Observable que no emite nada y se completa inmediatamente.throwError(error): Crea un Observable que no emite nada y solo emite un error.defer(() => Observable): Crea el Observable solo cuando se suscribe.
3.2. Operadores de Transformación
map(projectionFn): Aplica una función a cada valor emitido y emite el resultado.// RxJS of(1, 2, 3).pipe(map(x => x * 10)).subscribe(...) // Emite 10, 20, 30pluck(propertyName): Extrae una propiedad específica de los objetos emitidos.// RxJS of({ user: 'Alice' }, { user: 'Bob' }).pipe(pluck('user')).subscribe(...) // Emite 'Alice', 'Bob'scan(accumulator, initialValue): Similar areduce, mantiene un acumulador y emite cada estado intermedio.// RxJS of(1, 2, 3).pipe(scan((acc, val) => acc + val, 0)).subscribe(...) // Emite 1, 3, 6buffer(closingNotifier): Acumula valores hasta que elclosingNotifieremite, luego emite la lista acumulada.bufferTime(interval): Acumula valores durante unintervalode tiempo, luego emite la lista.debounceTime(ms): Emite un valor solo después de un período de inactividad demsmilisegundos (útil para autocompletado).switchMap(project): Mapea cada valor de origen a un Observable interno y se cancela cualquier Observable interno anterior. Útil para “typeahead” donde solo te importa la última búsqueda.mergeMap/flatMap(project): Mapea cada valor de origen a un Observable interno y fusiona todos los Observables internos en un solo flujo. El orden puede no ser el mismo que el origen.concatMap(project): Mapea cada valor de origen a un Observable interno y concatena los resultados en orden. Espera a que el Observable interno actual se complete antes de suscribirse al siguiente.exhaustMap(project): Mapea cada valor de origen a un Observable interno, pero ignora los nuevos valores de origen mientras el Observable interno actual esté activo. Útil para evitar múltiples clics de envío.
3.3. Operadores de Filtrado
filter(predicateFn): Emite solo los valores que cumplen una condición.// RxJS of(1, 2, 3, 4).pipe(filter(x => x % 2 === 0)).subscribe(...) // Emite 2, 4take(count): Emite solo los primeroscountvalores y luego completa.skip(count): Omite los primeroscountvalores.distinct()/distinctUntilChanged(): Emite solo valores únicos / o solo si son diferentes del último emitido.first()/last(): Emite solo el primer / último valor y luego completa.takeUntil(notifier): Emite valores hasta que elnotifierObservable emite.takeWhile(predicate): Emite valores mientras el predicado seatrue, luego completa.
3.4. Operadores de Combinación
merge(observable1, observable2, ...): Combina Observables, emitiendo valores tan pronto como son emitidos por cualquiera de los Observables de origen.concat(observable1, observable2, ...): Concatena Observables, emitiendo valores de uno en uno, en orden, esperando a que cada Observable anterior se complete.combineLatest(observable1, observable2, ...): Emite una tupla de los valores más recientes de cada Observable de origen cada vez que cualquiera de ellos emite un nuevo valor.forkJoin(observable1, observable2, ...): Espera a que todos los Observables de origen se completen, luego emite una tupla del último valor de cada uno. Similar aPromise.all().zip(observable1, observable2, ...): Emite una tupla de valores emparejados de cada Observable de origen, solo cuando todos los Observables hayan emitido un valor en esa posición.
3.5. Operadores de Utilidad
tap/do(callback): Realiza una acción de efecto secundario (ej. logging) para cada emisión, sin modificar el flujo.delay(ms): Retrasa las emisiones por un tiempo.timeout(ms): Si el flujo no emite enms, lanza un error.
4. ❌ Manejo de Errores
- Las notificaciones de error detienen el flujo por defecto.
catchError(errorHandlingFn): Captura errores en el flujo. La función de manejo puede devolver un nuevo Observable para recuperarse o lanzar un nuevo error.retry(count): Reintenta el Observable de origen si ocurre un error, hastacountveces.retryWhen(notifier): Reintenta el Observable de origen basado en la lógica de unnotifierObservable.
5. ⏱️ Concurrencia (Schedulers / Contexts)
- Los Schedulers (RxJS) o Contexts (Project Reactor, Kotlin Flows) determinan el contexto de ejecución de las operaciones.
- RxJS Schedulers:
asyncScheduler: Para operaciones asíncronas basadas ensetTimeout/setInterval.asapScheduler: Para operaciones asíncronas tan pronto como sea posible (microtasks).queueScheduler: Para operaciones síncronas pero en cola.animationFrameScheduler: Para operaciones relacionadas con la animación del navegador.
- Project Reactor Contexts: Se gestionan con métodos como
subscribeOn()(para el hilo de suscripción/creación) ypublishOn()(para el hilo de procesamiento de emisiones).
6. 🚦 Backpressure (Manejo de Contrapresión)
Cuando un productor genera datos más rápido de lo que un consumidor puede procesarlos.
- Estrategias comunes (implementadas por las librerías):
BUFFER: Almacena en un buffer los elementos hasta que el consumidor esté listo (riesgo de OutOfMemory).DROP: Descarta los elementos más nuevos si el buffer está lleno.LATEST: Descarta los elementos en el buffer, manteniendo solo el más reciente.ERROR: Lanza un error si el buffer se llena.
- Las librerías implementan internamente mecanismos de
request(pedir más elementos) para gestionar esto.
7. 📈 Beneficios
- Manejo Asíncrono Simplificado: Abstrae las complejidades de callbacks anidados, promesas y hilos.
- Código Declarativo y Conciso: Permite expresar la lógica de manipulación de flujos de forma más legible y compacta.
- Composición Poderosa: Las tuberías de operadores permiten construir lógica compleja a partir de funciones simples.
- Gestión de Errores Unificada: Los errores fluyen a través de la tubería como cualquier otro evento, permitiendo un manejo centralizado.
- Concurrencia Simplificada: Permite gestionar la ejecución en diferentes hilos/contextos de forma controlada.
- Mayor Resiliencia y Responsividad: Facilita la construcción de sistemas que responden bien a la carga y a los fallos.
8. ⚠️ Desafíos y Consideraciones
- Curva de Aprendizaje: Los conceptos de Observables, operadores y flujos pueden ser nuevos y requieren un cambio de mentalidad.
- Depuración: Las trazas de pila (stack traces) pueden ser largas y difíciles de seguir a través de múltiples operadores y hilos.
- Over-engineering: No todo problema necesita una solución reactiva. A veces, una promesa o
async/awaites suficiente. - Fugas de Memoria: Es crucial desuscribirse de los Observables de larga duración para evitar fugas de memoria (ej. en Angular, con
takeUntil(destroy$)oasyncpipe).
9. 💡 Buenas Prácticas y Consejos
- Desuscribirse (
unsubscribe()): Para Observables de larga duración (ej. eventos de UI, WebSockets), siempre desuscríbete cuando el componente/recurso ya no los necesite. - Isolar Efectos Secundarios: Realiza operaciones con efectos secundarios (ej. llamadas a la API, mutaciones DOM) solo en los puntos finales de la cadena o utilizando operadores como
tap(RxJS) /doOnNext(Reactor) de forma controlada. - Usar Operadores Puros: Las funciones pasadas a los operadores deben ser lo más puras posible.
- Error Handling Temprano: Maneja los errores lo antes posible en la cadena con
catchErroroonErrorResumeNextsi quieres que el flujo continúe. asyncPipe (Angular): Utiliza elasyncpipe en las plantillas de Angular para suscribirse y desuscribirse automáticamente de los Observables.- Evita Callbacks Anidados: Reemplaza los “callback hells” con cadenas de operadores reactivos.
- Hot vs. Cold Observables: Entiende la diferencia. Los Observables “fríos” (por defecto) empiezan a emitir valores desde el principio para cada suscriptor. Los “calientes” ya están emitiendo independientemente de los suscriptores.
- Utiliza Monitoreo: Herramientas de depuración específicas de la librería (ej. RxJS DevTools) son muy útiles.
Este cheatsheet te proporciona una referencia completa de la Programación Reactiva, cubriendo sus principios fundamentales, componentes clave, operadores comunes, manejo de errores y las mejores prácticas para construir sistemas eficientes y escalables.