Skip to content

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

typescript
// 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)

typescript
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�sticaPromiseObservable
Momento de ejecuci�nInmediato (Eager)Al suscribirse (Lazy)
N�mero de valoresSolo 1 vez0 o m�s veces (m�ltiples posibles)
Cancelaci�nNo posiblePosible (unsubscribe)
Reutilizaci�nResultado en cach�Re-ejecuci�n por suscripci�n (Cold)
Despu�s de errorTerminaTermina (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"

typescript
// 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 subscribe

L Malentendido 2: "subscribe retorna el valor"

typescript
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

typescript
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

typescript
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 camino

Caracter�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

typescript
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 Hot

C�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)

typescript
// 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)

typescript
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.

typescript
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 secundarios

Punto 2: No usar variables intermedias

typescript
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

typescript
// 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�n

Punto 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.

typescript
// 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
typescript
//  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.log o tap en cada etapa
  • Testeable - processed$ se puede probar independientemente
  • Reutilizable - clicks$ y processed$ 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

typescript
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: 42

Experimento 2: Diferencia entre Cold y Hot

typescript
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

typescript
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.

markdown
## 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 veces

Siguientes 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

Publicado bajo licencia CC-BY-4.0.