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
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
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
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
// FinEjemplo de Observable as�ncrono
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
| Observable | S�ncrono/As�ncrono | Raz�n |
|---|---|---|
of(1, 2, 3) | S�ncrono | Valores determinados inmediatamente |
from([1, 2, 3]) | S�ncrono | Se puede obtener inmediatamente del array |
interval(1000) | As�ncrono | Toma tiempo con temporizador |
fromEvent(button, 'click') | As�ncrono | Espera acci�n del usuario |
ajax('/api/data') | As�ncrono | Espera petici�n HTTP |
timer(1000) | As�ncrono | Emite despu�s de 1 segundo |
of(1).pipe(delay(100)) | As�ncrono | Se retrasa con delay |
Problema com�n: Mezcla de s�ncrono y as�ncrono
L Mal ejemplo: Orden no garantizado
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�ncronaBuen ejemplo: Aclarar intenci�n
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---|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: 30Ejemplo pr�ctico 2: merge
A: ----a----b----|
B: --c----d----e----|
merge(A, B)
Salida: --c-a--d-b--e----|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: B2Ejemplo 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|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 � CompletoRol del Scheduler
El Scheduler controla cu�ndo y c�mo emite valores el Observable.
Tipos de Scheduler
| Scheduler | Uso | Explicaci�n |
|---|---|---|
| queueScheduler | Procesamiento s�ncrono | Ejecuta inmediatamente en el event loop actual |
| asapScheduler | Microtarea | Mismo timing que Promise.then() |
| asyncScheduler | Macrotarea | Mismo timing que setTimeout() |
| animationFrameScheduler | Animaci�n | Mismo timing que requestAnimationFrame() |
Ejemplo pr�ctico: Controlar timing con observeOn
L Mal ejemplo: Procesamiento s�ncrono bloquea UI
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�lculoBuen ejemplo: Hacerlo as�ncrono con asyncScheduler
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 inmediatamenteCu�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
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
// FinPunto 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
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
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
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
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
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] 502. 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
- Buscar "RxJS DevTools" en Chrome Web Store
- Agregar extensi�n
- Abrir pesta�a "RxJS" de DevTools
3. Operador de depuraci�n personalizado
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: 204. Marble Testing (verificaci�n en tests)
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.
## 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 catchErrorSiguientes 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
- Chapter 7: Uso de Schedulers - Detalles del Scheduler
- Chapter 9: Marble Testing - Probar timing con TestScheduler
- Chapter 8: T�cnicas de depuraci�n de RxJS - Vista general de depuraci�n
- Selecci�n de operadores - C�mo elegir el operador apropiado
<� Ejercicios pr�cticos
Problema 1: Identificaci�n de s�ncrono y as�ncrono
�Los siguientes Observables son s�ncronos o as�ncronos?
// 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 -
ofemite valores inmediatamente - B: S�ncrono -
fromexpande array inmediatamente - C: As�ncrono - Incluso
delay(0)entra en cola as�ncrona - D: As�ncrono - Promise siempre es as�ncrono
- E: As�ncrono -
intervales 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.
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
// 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?
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, CCompletoRespuesta
C�digo corregido:
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 garantizadoconcatMap: 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.
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:
intervalemite valores infinitamente, por lo que no completatoArray()espera se�al de completado, por lo que nunca sale el valor
C�digo corregido:
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.