Skip to content

Comprensi�n de timing y orden

En RxJS, enfrentarse a problemas como "�Por qu� no sale el valor?" o "�El orden est� mal?" es muy com�n. Esta p�gina explica conocimientos fundamentales y t�cnicas pr�cticas de depuraci�n para entender correctamente el timing y el orden.

Cu�ndo fluyen los valores

Problema: Pensar que los valores salen inmediatamente despu�s de subscribe

Un error com�n de los principiantes es pensar que "si hago subscribe, puedo obtener el valor inmediatamente".

L Mal ejemplo: Esperar que el valor est� disponible inmediatamente

typescript
import { of } from 'rxjs';
import { delay } from 'rxjs';

let result: number | undefined;

of(42).pipe(
  delay(100)
).subscribe(value => {
  result = value;
});

console.log(result); // undefined (el valor a�n no lleg�)

 Buen ejemplo: Procesar dentro de subscribe

typescript
import { of } from 'rxjs';
import { delay } from 'rxjs';

of(42).pipe(
  delay(100)
).subscribe(value => {
  console.log(value); // 42 se imprime despu�s de 100ms
});

Principio importante

  • Observable puede ser as�ncrono
  • El procesamiento que usa valores debe hacerse dentro de subscribe
  • No esperar valores fuera de subscribe

Comprensi�n de s�ncrono vs as�ncrono

Observable s�ncrono vs Observable as�ncrono

En RxJS hay Observables que fluyen valores s�ncronamente y Observables que fluyen valores as�ncronamente.

Ejemplo de Observable s�ncrono

typescript
import { of } from 'rxjs';

console.log('Inicio');

of(1, 2, 3).subscribe(value => {
  console.log('Valor:', value);
});

console.log('Fin');

// Salida:
// Inicio
// Valor: 1
// Valor: 2
// Valor: 3
// Fin

Ejemplo de Observable as�ncrono

typescript
import { interval } from 'rxjs';
import { take } from 'rxjs';

console.log('Inicio');

interval(100).pipe(
  take(3)
).subscribe(value => {
  console.log('Valor:', value);
});

console.log('Fin');

// Salida:
// Inicio
// Fin
// Valor: 0  (despu�s de 100ms)
// Valor: 1  (despu�s de 200ms)
// Valor: 2  (despu�s de 300ms)

Visualizaci�n del flujo de ejecuci�n s�ncrono vs as�ncrono

El siguiente diagrama de secuencia muestra las diferencias de timing de ejecuci�n entre Observable s�ncrono y as�ncrono.

Diferencias de timing

  • Observable s�ncrono: Avanza a la siguiente l�nea despu�s de completar el procesamiento dentro de subscribe
  • Observable as�ncrono: subscribe retorna inmediatamente, valores fluyen despu�s

Criterios de juicio s�ncrono/as�ncrono

ObservableS�ncrono/As�ncronoRaz�n
of(1, 2, 3)S�ncronoValores determinados inmediatamente
from([1, 2, 3])S�ncronoSe puede obtener inmediatamente del array
interval(1000)As�ncronoToma tiempo con temporizador
fromEvent(button, 'click')As�ncronoEspera acci�n del usuario
ajax('/api/data')As�ncronoEspera petici�n HTTP
timer(1000)As�ncronoEmite despu�s de 1 segundo
of(1).pipe(delay(100))As�ncronoSe retrasa con delay

Problema com�n: Mezcla de s�ncrono y as�ncrono

L Mal ejemplo: Orden no garantizado

typescript
import { of } from 'rxjs';
import { delay } from 'rxjs';

console.log('1: Inicio');

of('S�ncrono').subscribe(value => {
  console.log('2:', value);
});

of('As�ncrono').pipe(
  delay(0) // Incluso con 0ms se vuelve as�ncrono
).subscribe(value => {
  console.log('3:', value);
});

console.log('4: Fin');

// Salida:
// 1: Inicio
// 2: S�ncrono
// 4: Fin
// 3: As�ncrono  � Incluso delay(0) entra en cola as�ncrona

 Buen ejemplo: Aclarar intenci�n

typescript
import { of, concat } from 'rxjs';
import { delay } from 'rxjs';

// Si se quiere garantizar el orden, usar concat
concat(
  of('Primero'),
  of('Siguiente').pipe(delay(100)),
  of('�ltimo')
).subscribe(value => {
  console.log(value);
});

// Salida:
// Primero
// Siguiente    (despu�s de 100ms)
// �ltimo  (despu�s de 100ms)

C�mo leer Marble Diagrams

Un Marble Diagram es una figura que visualiza el comportamiento de Observable en el eje temporal.

Notaci�n b�sica

Eje temporal:  ------a----b----c----|
               �     �    �    �    �
               Inicio  Valor a  Valor b  Valor c  Completo

Significado de s�mbolos:
-  : Paso del tiempo (aprox. 10ms)
a  : Emisi�n de valor (next)
|  : Completado (complete)
#  : Error (error)
() : Emisi�n simult�nea (a,b)

Ejemplo pr�ctico 1: Operador map

Entrada:  ----1----2----3----|
          map(x => x * 10)
Salida:   ----10---20---30---|
typescript
import { of } from 'rxjs';
import { map, delay, concatMap } from 'rxjs';

of(1, 2, 3).pipe(
  concatMap(v => of(v).pipe(delay(100))), // Fluir cada 100ms
  map(x => x * 10)
).subscribe(value => console.log(value));

// 100ms: 10
// 200ms: 20
// 300ms: 30

Ejemplo pr�ctico 2: merge

A:     ----a----b----|
B:     --c----d----e----|
       merge(A, B)
Salida:  --c-a--d-b--e----|
typescript
import { interval, merge } from 'rxjs';
import { map, take } from 'rxjs';

const a$ = interval(200).pipe(
  map(i => `A${i}`),
  take(2)
);

const b$ = interval(150).pipe(
  map(i => `B${i}`),
  take(3)
);

merge(a$, b$).subscribe(value => console.log(value));

// 150ms: B0
// 200ms: A0
// 300ms: B1
// 400ms: A1
// 450ms: B2

Ejemplo pr�ctico 3: switchMap (cancelaci�n)

Externo:  ----A------B----C----|
          switchMap(x => interno)
Interno A: ----1--2|  (cancelado por B)
Interno B:        ----3--4|  (cancelado por C)
Interno C:             ----5--6|
Salida:   ----1------3----5--6|
typescript
import { fromEvent, interval } from 'rxjs';
import { switchMap, map, take } from 'rxjs';

const button = document.querySelector('button')!;

fromEvent(button, 'click').pipe(
  switchMap(() =>
    interval(100).pipe(
      map(i => `Valor${i}`),
      take(3)
    )
  )
).subscribe(value => console.log(value));

// Clic1 � Valor0 � Valor1 � (siguiente cancelado por Clic2)
// Clic2 � Valor0 � Valor1 � Valor2 � Completo

Rol del Scheduler

El Scheduler controla cu�ndo y c�mo emite valores el Observable.

Tipos de Scheduler

SchedulerUsoExplicaci�n
queueSchedulerProcesamiento s�ncronoEjecuta inmediatamente en el event loop actual
asapSchedulerMicrotareaMismo timing que Promise.then()
asyncSchedulerMacrotareaMismo timing que setTimeout()
animationFrameSchedulerAnimaci�nMismo timing que requestAnimationFrame()

Ejemplo pr�ctico: Controlar timing con observeOn

L Mal ejemplo: Procesamiento s�ncrono bloquea UI

typescript
import { range } from 'rxjs';
import { map } from 'rxjs';

console.log('Inicio');

range(1, 1000000).pipe(
  map(x => x * x)
).subscribe(value => {
  // 1 mill�n de c�lculos ejecutados s�ncronamente � UI congelada
});

console.log('Fin'); // Se imprime despu�s de terminar el c�lculo

 Buen ejemplo: Hacerlo as�ncrono con asyncScheduler

typescript
import { range, asyncScheduler } from 'rxjs';
import { map, observeOn } from 'rxjs';

console.log('Inicio');

range(1, 1000000).pipe(
  map(x => x * x),
  observeOn(asyncScheduler) // Poner en cola as�ncrona
).subscribe(value => {
  // Se ejecuta as�ncronamente � UI no se bloquea
});

console.log('Fin'); // Se imprime inmediatamente

Cu�ndo usar Scheduler

  • C�lculos pesados: Hacerlo as�ncrono con asyncScheduler para no bloquear UI
  • Animaciones: Renderizado suave con animationFrameScheduler
  • Tests: Virtualizar tiempo con TestScheduler

Ver detalles en Chapter 7: Uso de Schedulers.

Problemas comunes y m�todos de depuraci�n

Problema 1: Valores no fluyen

Lista de verificaci�n

T�cnica de depuraci�n: Usar tap

typescript
import { of } from 'rxjs';
import { map, filter, tap } from 'rxjs';

console.log('Inicio');

of(1, 2, 3, 4, 5).pipe(
  tap(v => console.log('=A Valor original:', v)),
  filter(x => x % 2 === 0),
  tap(v => console.log(' Pas� filter:', v)),
  map(x => x * 10),
  tap(v => console.log('= Despu�s de map:', v))
).subscribe(result => {
  console.log('=� Resultado final:', result);
});

console.log('Fin');

// Salida:
// Inicio
// =A Valor original: 1
// =A Valor original: 2
//  Pas� filter: 2
// = Despu�s de map: 20
// =� Resultado final: 20
// =A Valor original: 3
// =A Valor original: 4
//  Pas� filter: 4
// = Despu�s de map: 40
// =� Resultado final: 40
// =A Valor original: 5
// Fin

Punto clave

Como of() es Observable s�ncrono, "Fin" se imprime despu�s de completar todo el procesamiento dentro de subscribe. Intercalando tap en cada etapa, se puede rastrear el flujo de valores.

Problema 2: Orden diferente al esperado

L Mal ejemplo: Orden se desordena con mergeMap

typescript
import { of } from 'rxjs';
import { mergeMap, delay } from 'rxjs';

of(1, 2, 3).pipe(
  mergeMap(x =>
    of(x * 10).pipe(
      delay(Math.random() * 100) // Retraso aleatorio
    )
  )
).subscribe(value => console.log(value));

// Ejemplo de salida: 20, 10, 30 (orden no garantizado)

 Buen ejemplo: Garantizar orden con concatMap

typescript
import { of } from 'rxjs';
import { concatMap, delay } from 'rxjs';

of(1, 2, 3).pipe(
  concatMap(x =>
    of(x * 10).pipe(
      delay(Math.random() * 100)
    )
  )
).subscribe(value => console.log(value));

// Salida: 10, 20, 30 (siempre este orden)

Problema 3: No completa (stream infinito)

L Mal ejemplo: Se atasca con operador que espera completado

typescript
import { interval } from 'rxjs';
import { reduce } from 'rxjs';

interval(1000).pipe(
  reduce((acc, val) => acc + val, 0) // Nunca completa
).subscribe(total => {
  console.log(total); // Esta l�nea no se ejecuta
});

 Buen ejemplo: Delimitar con take

typescript
import { interval } from 'rxjs';
import { reduce, take } from 'rxjs';

interval(1000).pipe(
  take(5),                            // Obtener solo 5
  reduce((acc, val) => acc + val, 0) // Sumar despu�s de completar
).subscribe(total => {
  console.log('Total:', total); // "Total: 10" se imprime despu�s de 5 segundos
});

Herramientas y t�cnicas de depuraci�n

1. Salida de logs con tap

typescript
import { of } from 'rxjs';
import { map, filter, tap } from 'rxjs';

const debug = <T>(label: string) => tap<T>(value =>
  console.log(`[${label}]`, value)
);

of(1, 2, 3, 4, 5).pipe(
  debug('=5 Entrada'),
  filter(x => x > 2),
  debug('=� Despu�s de filter'),
  map(x => x * 10),
  debug('=� Despu�s de map')
).subscribe();

// [=5 Entrada] 1
// [=5 Entrada] 2
// [=5 Entrada] 3
// [=� Despu�s de filter] 3
// [=� Despu�s de map] 30
// [=5 Entrada] 4
// [=� Despu�s de filter] 4
// [=� Despu�s de map] 40
// [=5 Entrada] 5
// [=� Despu�s de filter] 5
// [=� Despu�s de map] 50

2. RxJS DevTools (extensi�n de navegador)

Con la extensi�n de Chrome/Edge "RxJS DevTools", se puede:

  • Monitorear todos los Observables en tiempo real
  • Visualizaci�n con Marble Diagram
  • Rastreo de subscribe/unsubscribe

M�todo de instalaci�n

  1. Buscar "RxJS DevTools" en Chrome Web Store
  2. Agregar extensi�n
  3. Abrir pesta�a "RxJS" de DevTools

3. Operador de depuraci�n personalizado

typescript
import { interval, map, take, tap, timestamp } from "rxjs";
import { MonoTypeOperatorFunction } from 'rxjs';


function debugWithTime<T>(label: string): MonoTypeOperatorFunction<T> {
  return source => source.pipe(
    timestamp(),
    tap(({ value, timestamp }) => {
      console.log(`[${label}] ${new Date(timestamp).toISOString()}:`, value);
    }),
    map(({ value }) => value)
  );
}

// Uso
interval(500).pipe(
  take(3),
  debugWithTime('� Temporizador'),
  map(x => x * 10),
  debugWithTime('= Despu�s de conversi�n')
).subscribe();

// [� Temporizador] 2025-10-19T10:20:59.467Z: 0
// [= Despu�s de conversi�n] 2025-10-19T10:20:59.467Z: 0
// [� Temporizador] 2025-10-19T10:20:59.967Z: 1
// [= Despu�s de conversi�n] 2025-10-19T10:20:59.967Z: 10
// [� Temporizador] 2025-10-19T10:21:00.467Z: 2
// [= Despu�s de conversi�n] 2025-10-19T10:21:00.468Z: 20

4. Marble Testing (verificaci�n en tests)

typescript
import { TestScheduler } from 'rxjs/testing';
import { map } from 'rxjs';

describe('Test de timing', () => {
  let scheduler: TestScheduler;

  beforeEach(() => {
    scheduler = new TestScheduler((actual, expected) => {
      expect(actual).toEqual(expected);
    });
  });

  it('map transforma valores', () => {
    scheduler.run(({ cold, expectObservable }) => {
      const input$  = cold('--a--b--c--|', { a: 1, b: 2, c: 3 });
      const expected =     '--x--y--z--|';
      const result$ = input$.pipe(map(v => v * 10));

      expectObservable(result$).toBe(expected, { x: 10, y: 20, z: 30 });
    });
  });
});

Ver detalles en Chapter 9: Marble Testing.

Lista de verificaci�n de comprensi�n

Verifique si puede responder las siguientes preguntas.

markdown
## Comprensi�n b�sica
- [ ] Explicar las diferencias entre Observable s�ncrono y as�ncrono
- [ ] Leer notaci�n b�sica de Marble Diagram (-, a, |, #)
- [ ] Entender que valores no fluyen sin subscribe

## Control de timing
- [ ] Explicar las diferencias entre delay, debounceTime y throttleTime
- [ ] Entender el rol del Scheduler
- [ ] Explicar las diferencias entre observeOn y subscribeOn

## Depuraci�n
- [ ] Depurar flujo de valores con tap
- [ ] Identificar causas de valores que no fluyen
- [ ] Saber c�mo manejar orden diferente al esperado

## Pr�ctica
- [ ] Delimitar Observable infinito con take
- [ ] Implementar diferencias de orden entre mergeMap y concatMap
- [ ] Controlar timing de error con catchError

Siguientes pasos

Despu�s de entender timing y orden, aprenda sobre gesti�n de estado y compartici�n.

Dificultad de gesti�n de estado - Diferencias entre Subject, share/shareReplay

P�ginas relacionadas

<� Ejercicios pr�cticos

Problema 1: Identificaci�n de s�ncrono y as�ncrono

�Los siguientes Observables son s�ncronos o as�ncronos?

typescript
// A
of(1, 2, 3)

// B
from([1, 2, 3])

// C
of(1, 2, 3).pipe(delay(0))

// D
Promise.resolve(42)

// E
interval(1000).pipe(take(3))
Respuesta
  • A: S�ncrono - of emite valores inmediatamente
  • B: S�ncrono - from expande array inmediatamente
  • C: As�ncrono - Incluso delay(0) entra en cola as�ncrona
  • D: As�ncrono - Promise siempre es as�ncrono
  • E: As�ncrono - interval es basado en temporizador

Punto clave

delay(0) y Promise, aunque el retraso sea 0 milisegundos, se tratan como as�ncronos.

Problema 2: Lectura de Marble Diagram

Prediga la salida del siguiente Marble Diagram.

typescript
import { of, zip } from 'rxjs';
import { delay } from 'rxjs';

const a$ = of(1, 2, 3);
const b$ = of('A', 'B', 'C').pipe(delay(100));

zip(a$, b$).subscribe(console.log);
Marble Diagram:
a$:  (123)|
b$:  -----(ABC)|
     zip(a$, b$)
Salida: ?
Respuesta
typescript
// Despu�s de 100ms, se imprime todo a la vez:
[1, 'A']
[2, 'B']
[3, 'C']

Raz�n

Como zip espera hasta que valores de ambos streams est�n listos, no se imprime hasta que se libere el delay(100) de b$. a$ emite valores s�ncronamente, pero espera a b$ para formar pares.

Problema 3: Garant�a de orden

�Qu� operador usar si se quiere garantizar el orden de salida en el siguiente c�digo?

typescript
import { of } from 'rxjs';
import { mergeMap, delay } from 'rxjs';

of('A', 'B', 'C').pipe(
  mergeMap(letter =>
    of(`${letter}Completo`).pipe(
      delay(Math.random() * 100)
    )
  )
).subscribe(console.log);

// Salida actual: Orden aleatorio (ej: BCompleto, ACompleto, CCompleto)
// Salida esperada: ACompleto, BCompleto, CCompleto
Respuesta

C�digo corregido:

typescript
import { of } from 'rxjs';
import { concatMap, delay } from 'rxjs';

of('A', 'B', 'C').pipe(
  concatMap(letter =>  // mergeMap � concatMap
    of(`${letter}Completo`).pipe(
      delay(Math.random() * 100)
    )
  )
).subscribe(console.log);

// Salida: ACompleto, BCompleto, CCompleto (siempre este orden)

Raz�n

  • mergeMap: Ejecuta en paralelo, orden de completado no garantizado
  • concatMap: Ejecuta secuencialmente, salida en el mismo orden que entrada

Problema 4: Manejo de stream infinito

Se�ale el problema del siguiente c�digo y corr�jalo.

typescript
import { interval } from 'rxjs';
import { map, toArray } from 'rxjs';

interval(1000).pipe(
  map(x => x * 2),
  toArray()
).subscribe(arr => {
  console.log('Array:', arr); // �Se ejecuta esta l�nea?
});
Respuesta

Problema:

  • interval emite valores infinitamente, por lo que no completa
  • toArray() espera se�al de completado, por lo que nunca sale el valor

C�digo corregido:

typescript
import { interval } from 'rxjs';
import { map, take, toArray } from 'rxjs';

interval(1000).pipe(
  take(5),          // Obtener solo 5 y completar
  map(x => x * 2),
  toArray()
).subscribe(arr => {
  console.log('Array:', arr); // [0, 2, 4, 6, 8]
});

Punto clave

Al usar "operadores que esperan completado" como reduce, toArray, last en streams infinitos, es necesario delimitarlos con take, first, takeUntil, etc.

Publicado bajo licencia CC-BY-4.0.