El Muro de la Comprensi�n Conceptual
El primer muro de RxJS es la comprensi�n de conceptos. Especialmente para desarrolladores acostumbrados a Promises, el comportamiento de Observable puede ser contraintuitivo.
Diferencias Esenciales entre Observable vs Promise
Promise: Procesamiento As�ncrono de Una Sola Vez
// Promise: retorna un valor solo una vez
const userPromise = fetch('/api/user/1').then(res => res.json());
userPromise.then(user => console.log(user)); // Obtiene informaci�n del usuario solo una vez
userPromise.then(user => console.log(user)); // Mismo resultado en cach�Caracter�sticas
- Eager (ejecuci�n inmediata) - El procesamiento comienza en el momento de creaci�n de Promise
- Completa solo una vez - �xito o fallo, solo una vez
- No cancelable - Una vez iniciado, no se puede detener
- Siempre Hot - M�ltiples then comparten el mismo resultado
Observable: Stream (Flujo de Datos con Eje Temporal)
import { Observable } from 'rxjs';
// Observable: fluye m�ltiples valores a lo largo del eje temporal
const user$ = new Observable<User>(subscriber => {
console.log('�Inicio de ejecuci�n de Observable!');
fetch('/api/user/1')
.then(res => res.json())
.then(user => {
subscriber.next(user);
subscriber.complete();
});
});
// L Nada sucede a�n en este punto (Lazy)
console.log('Creaci�n de Observable completada');
// Se ejecuta solo cuando se suscribe
user$.subscribe(user => console.log('Suscripci�n1:', user));
user$.subscribe(user => console.log('Suscripci�n2:', user));
// � La llamada API se ejecuta 2 veces (Cold Observable)Salida
Creaci�n de Observable completada
�Inicio de ejecuci�n de Observable!
Suscripci�n1: { id: 1, name: 'Alice' }
�Inicio de ejecuci�n de Observable!
Suscripci�n2: { id: 1, name: 'Alice' }Caracter�sticas
- Lazy (ejecuci�n diferida) - Nada sucede hasta que se suscribe
- Puede fluir m�ltiples valores - Se puede llamar next() m�ltiples veces
- Cancelable - Se puede detener con unsubscribe
- Cold o Hot - Se puede elegir si ejecutar por suscripci�n o compartir
Tabla de Comparaci�n
| Caracter�stica | Promise | Observable |
|---|---|---|
| Momento de ejecuci�n | Inmediato (Eager) | Al suscribirse (Lazy) |
| N�mero de valores | Solo 1 vez | 0 o m�s veces (m�ltiples posibles) |
| Cancelaci�n | No posible | Posible (unsubscribe) |
| Reutilizaci�n | Resultado en cach� | Re-ejecuci�n por suscripci�n (Cold) |
| Despu�s de error | Termina | Termina (retry posible) |
Visualizaci�n de las Diferencias de Comportamiento
El siguiente diagrama de secuencia muestra las diferencias en el flujo de ejecuci�n entre Promise y Observable.
Malentendidos Comunes
L Malentendido 1: "Observable es solo para asincron�a"
// Observable tambi�n puede manejar procesamiento s�ncrono
import { of } from 'rxjs';
const sync$ = of(1, 2, 3);
console.log('Before subscribe');
sync$.subscribe(value => console.log(value));
console.log('After subscribe');
// Salida (se ejecuta s�ncronamente):
// Before subscribe
// 1
// 2
// 3
// After subscribeL Malentendido 2: "subscribe retorna el valor"
import { map, of } from "rxjs";
const observable$ = of(1, 2, 3);
// L Ejemplo malo: Pensamiento tipo Promise
const value = observable$.subscribe(x => x); // value es un objeto Subscription
console.log(value); // Subscription { ... } � No es el valor esperado
// Ejemplo bueno: Pensamiento tipo Observable
observable$.pipe(
map(x => x * 2)
).subscribe(value => {
console.log(value); // Usar el valor aqu�
});Comprensi�n Intuitiva de Cold vs Hot
Cold Observable: Stream Independiente por Suscripci�n
import { interval } from 'rxjs';
import { take } from 'rxjs';
// Cold: cada suscriptor tiene su propio temporizador independiente
const cold$ = interval(1000).pipe(take(3));
console.log('Inicio Suscripci�n1');
cold$.subscribe(x => console.log('Suscripci�n1:', x));
setTimeout(() => {
console.log('Inicio Suscripci�n2 (despu�s de 2s)');
cold$.subscribe(x => console.log('Suscripci�n2:', x));
}, 2000);
// Salida:
// Inicio Suscripci�n1
// Suscripci�n1: 0 (despu�s de 1s)
// Suscripci�n1: 1 (despu�s de 2s)
// Inicio Suscripci�n2 (despu�s de 2s)
// Suscripci�n1: 2 (despu�s de 3s)
// Suscripci�n2: 0 (despu�s de 3s) � Suscripci�n2 comienza desde el principio
// Suscripci�n2: 1 (despu�s de 4s)
// Suscripci�n2: 2 (despu�s de 5s)Caracter�sticas de Cold
- Ejecuci�n independiente por suscripci�n
- Mantiene el "plano" de los datos
- Ejemplos: Llamadas HTTP API, temporizadores, lectura de archivos
Hot Observable: Todos los Suscriptores Comparten el Mismo Stream
import { interval } from 'rxjs';
import { take, share } from 'rxjs';
// Hot: compartido con share()
const hot$ = interval(1000).pipe(
take(3),
share() // Esto lo hace Hot
);
console.log('Inicio Suscripci�n1');
hot$.subscribe(x => console.log('Suscripci�n1:', x));
setTimeout(() => {
console.log('Inicio Suscripci�n2 (despu�s de 2s)');
hot$.subscribe(x => console.log('Suscripci�n2:', x));
}, 2000);
// Salida:
// Inicio Suscripci�n1
// Suscripci�n1: 0 (despu�s de 1s)
// Suscripci�n1: 1 (despu�s de 2s)
// Inicio Suscripci�n2 (despu�s de 2s)
// Suscripci�n1: 2 (despu�s de 3s)
// Suscripci�n2: 2 (despu�s de 3s) � Suscripci�n2 se une a mitad de caminoCaracter�sticas de Hot
- Todos los suscriptores comparten la misma ejecuci�n
- Los datos est�n siendo "transmitidos"
- Ejemplos: Eventos de clic, WebSocket, Subject
C�mo Distinguir Cold/Hot
import { fromEvent, interval, of, Subject } from 'rxjs';
import { share } from 'rxjs';
// Cold (ejecuci�n independiente por suscripci�n)
const cold1$ = of(1, 2, 3);
const cold2$ = interval(1000);
const cold3$ = ajax('/api/data');
const cold4$ = fromEvent(button, 'click'); // Cold pero especial
// Hot (compartido entre suscriptores)
const hot1$ = new Subject<number>();
const hot2$ = interval(1000).pipe(share()); // Convierte Cold a HotC�mo Distinguirlos
- Creation Functions (of, from, fromEvent, interval, ajax, etc.) � Cold
- Familia Subject � Hot
- Uso de share(), shareReplay() � Convierte Cold a Hot
Cambio de Pensamiento hacia Programaci�n Declarativa
Imperativo vs Declarativo
RxJS es un paradigma de programaci�n declarativa.
L Pensamiento Imperativo (Promise/async-await)
// Imperativo: describe "c�mo" procesar
async function processUsers() {
const response = await fetch('/api/users');
const users = await response.json();
const activeUsers = [];
for (const user of users) {
if (user.isActive) {
activeUsers.push(user);
}
}
const userNames = [];
for (const user of activeUsers) {
userNames.push(user.name.toUpperCase());
}
return userNames;
}Pensamiento Declarativo (RxJS)
import { from } from 'rxjs';
import { mergeMap, filter, map, toArray } from 'rxjs';
// Declarativo: describe "qu�" transformar
const processUsers$ = from(fetch('/api/users')).pipe(
mergeMap(res => res.json()),
mergeMap(users => users), // Expande el array
filter(user => user.isActive),
map(user => user.name.toUpperCase()),
toArray()
);
processUsers$.subscribe(userNames => console.log(userNames));Diferencia
- Imperativo: Describe procedimientos (bucles, condicionales, asignaci�n de variables)
- Declarativo: Describe pipeline de transformaci�n (flujo de datos)
Puntos Clave para el Cambio de Pensamiento
Punto 1: No procesar datos dentro de subscribe
Las transformaciones de datos dentro de pipe, subscribe solo para efectos secundarios.
import { filter, map, of } from "rxjs";
const observable$ = of(1, 2, 3);
// L Ejemplo malo: procesar dentro de subscribe
observable$.subscribe(value => {
const doubled = value * 2; // =H C�lculo dentro de subscribe
const filtered = doubled > 4 ? doubled : null; // =H Condicional dentro de subscribe
if (filtered) { // =H Sentencia if dentro de subscribe
console.log(filtered);
}
});
// Ejemplo bueno: transformar dentro de pipe
observable$.pipe(
map(value => value * 2), // C�lculo dentro de pipe
filter(value => value > 4) // Filtrado tambi�n dentro de pipe
).subscribe(value => console.log(value)); // subscribe solo para efectos secundariosPunto 2: No usar variables intermedias
import { filter, map, Observable, of } from "rxjs";
const source$ = of(1, 2, 3, 4, 5);
// L Ejemplo malo: guardar en variables intermedias
let doubled$: Observable<number>; // =H Declarar variable intermedia
let filtered$: Observable<number>; // =H Declarar variable intermedia
doubled$ = source$.pipe(map(x => x * 2)); // =H Asignar a variable intermedia
filtered$ = doubled$.pipe(filter(x => x > 5)); // =H Asignar a variable intermedia
filtered$.subscribe(console.log);
// Ejemplo bueno: conectar con pipeline
source$.pipe(
map(x => x * 2), // Conectar directamente en pipeline
filter(x => x > 5) // Conectar directamente en pipeline
).subscribe(console.log);Punto 3: Evitar subscribe anidados
// L Ejemplo malo: subscribe anidado
getUser$(userId).subscribe(user => {
getOrders$(user.id).subscribe(orders => { // =H subscribe adicional dentro de subscribe (anidado)
console.log(user, orders);
}); // =H La cancelaci�n de suscripci�n se vuelve compleja
});
// Ejemplo bueno: aplanar con mergeMap
getUser$(userId).pipe(
mergeMap(user => // mergeMap aplana el Observable interno
getOrders$(user.id).pipe(
map(orders => ({ user, orders }))
)
)
).subscribe(({ user, orders }) => console.log(user, orders)); // Solo una suscripci�nPunto 4: Organizar con sintaxis de separaci�n en 3 etapas
Una t�cnica importante para mejorar dr�sticamente la legibilidad y mantenibilidad del c�digo RxJS es la sintaxis de separaci�n por etapas.
// L Ejemplo malo: one-liner con todo mezclado
fromEvent(document, 'click').pipe(
map(event => (event as MouseEvent).clientX),
filter(x => x > 100),
throttleTime(200)
).subscribe({
next: x => console.log('Posici�n del clic:', x),
error: err => console.error(err)
});Problemas
- Definici�n de stream, transformaci�n y suscripci�n mezcladas
- Depuraci�n dif�cil (no se sabe d�nde ocurre el problema)
- Dif�cil de probar
- No reutilizable
// Ejemplo bueno: separado en 3 etapas
import { filter, fromEvent, map, throttleTime } from "rxjs";
// 1. Definici�n de Observable (fuente del stream)
const clicks$ = fromEvent(document, 'click');
// 2. Definici�n de pipeline (procesamiento de transformaci�n de datos)
const processed$ = clicks$.pipe(
map(event => (event as MouseEvent).clientX),
filter(x => x > 100),
throttleTime(200)
);
// 3. Procesamiento de suscripci�n (ejecuci�n de efectos secundarios)
const subscription = processed$.subscribe({
next: x => console.log('Posici�n del clic:', x),
error: err => console.error(err),
complete: () => console.log('Completado')
});Beneficios
- Depuraci�n f�cil - Se puede insertar
console.logotapen cada etapa - Testeable -
processed$se puede probar independientemente - Reutilizable -
clicks$yprocessed$se pueden usar en otros lugares - Mejora la legibilidad - La intenci�n del c�digo se vuelve clara
La sintaxis de separaci�n por etapas es una de las t�cnicas m�s pr�cticas para superar las dificultades de RxJS.
Para m�s detalles, consulta Cap�tulo 10: Infierno de One-liners y Sintaxis de Separaci�n por Etapas.
Experimentar para Comprender (Utilizar Starter Kit)
Experimento 1: Diferencia entre Lazy y Eager
import { Observable } from 'rxjs';
console.log('=== Promise (Eager) ===');
const promise = new Promise((resolve) => {
console.log('�Ejecuci�n de Promise!');
resolve(42);
});
console.log('Creaci�n de Promise completada');
promise.then(value => console.log('Resultado de Promise:', value));
console.log('\n=== Observable (Lazy) ===');
const observable$ = new Observable(subscriber => {
console.log('�Ejecuci�n de Observable!');
subscriber.next(42);
subscriber.complete();
});
console.log('Creaci�n de Observable completada');
observable$.subscribe(value => console.log('Resultado de Observable:', value));
// Salida:
// === Promise (Eager) ===
// �Ejecuci�n de Promise!
// Creaci�n de Promise completada
// Resultado de Promise: 42
//
// === Observable (Lazy) ===
// Creaci�n de Observable completada
// �Ejecuci�n de Observable!
// Resultado de Observable: 42Experimento 2: Diferencia entre Cold y Hot
import { interval } from 'rxjs';
import { take, share } from 'rxjs';
// Cold: independiente por suscripci�n
const cold$ = interval(1000).pipe(take(3));
console.log('Cold Observable:');
cold$.subscribe(x => console.log('Cold Suscripci�n1:', x));
setTimeout(() => {
cold$.subscribe(x => console.log('Cold Suscripci�n2:', x));
}, 2000);
// Hot: compartido
const hot$ = interval(1000).pipe(take(3), share());
setTimeout(() => {
console.log('\nHot Observable:');
hot$.subscribe(x => console.log('Hot Suscripci�n1:', x));
setTimeout(() => {
hot$.subscribe(x => console.log('Hot Suscripci�n2:', x));
}, 2000);
}, 6000);Ejecuta esto en el entorno de ejecuci�n de aprendizaje para experimentar la diferencia.
Experimento 3: Declarativo vs Imperativo
import { of } from 'rxjs';
import { map, filter } from 'rxjs';
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Imperativo
console.log('=== Imperativo ===');
const result1: number[] = [];
for (const num of numbers) {
const doubled = num * 2;
if (doubled > 10) {
result1.push(doubled);
}
}
console.log(result1);
// Declarativo
console.log('\n=== Declarativo ===');
of(...numbers).pipe(
map(num => num * 2),
filter(num => num > 10)
).subscribe(num => console.log(num));Verificaci�n de Comprensi�n
Verifica si puedes responder las siguientes preguntas.
## Conceptos B�sicos
- [ ] Puedes enumerar 3 diferencias entre Promise y Observable
- [ ] Puedes explicar la diferencia entre Lazy y Eager
- [ ] Puedes explicar la diferencia entre Cold y Hot con ejemplos
## Pr�ctica
- [ ] Puedes explicar por qu� no se debe completar el procesamiento dentro de subscribe
- [ ] Sabes c�mo corregir subscribe anidados
- [ ] Conoces c�mo convertir un Cold Observable a Hot
## Depuraci�n
- [ ] Puedes identificar la causa cuando un Observable no se ejecuta
- [ ] Comprendes la causa de que la suscripci�n se ejecute m�ltiples vecesSiguientes Pasos
Una vez que comprendas los conceptos, avanza a los muros m�s pr�cticos.
- El Muro de la Gesti�n del Ciclo de Vida (En preparaci�n) - Cu�ndo hacer subscribe/unsubscribe
- La Indecisi�n de Selecci�n de Operadores (En preparaci�n) - Criterios para elegir entre m�s de 100 operadores
Secciones Relacionadas
- �Qu� es RxJS? - Conceptos b�sicos de RxJS
- Diferencias entre Promise y RxJS - Promise vs Observable
- Cold and Hot Observables - Explicaci�n detallada de Cold/Hot