Skip to content

La barrera de la depuración ​

Cuando te enfrentas a problemas en RxJS como "los valores no fluyen", "aparecen valores diferentes a los esperados" o "podría haber una fuga de memoria", resolverlos puede llevar mucho tiempo si no conoces los métodos de depuración adecuados. Esta página explica de manera integral las técnicas de depuración específicas de RxJS.

Estrategia básica de depuración en RxJS ​

Los 5 pasos de la depuración ​

Paso 1: Identificar el problema ​

Primero, clarifica qué es el problema.

SíntomaPosibles causas
No fluyen valoresOlvidaste subscribe, termina antes de complete, excluido por filter
No aparece el primer valorcombineLatest no cumple condición inicial, BehaviorSubject no configurado
El orden es extrañoUso de mergeMap, timing asíncrono
Aparecen valores duplicadosMúltiples subscribe sin share, mal uso de shareReplay
Hay fuga de memoriaOlvidaste unsubscribe, shareReplay con refCount: false
Los valores se retrasandebounceTime, throttleTime, procesamiento asíncrono

Paso 2: Formular hipótesis ​

Supón la causa del problema.

typescript
// Ejemplo: Problema de "no aparecen valores"
// Hipótesis 1: ¿No hay subscribe?
// Hipótesis 2: ¿complete/error demasiado temprano?
// Hipótesis 3: ¿Excluido por filter?
// Hipótesis 4: ¿Demora por procesamiento asíncrono?

Paso 3: Verificar con tap ​

Inserta tap en cada etapa para confirmar qué está ocurriendo realmente.

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

of(1, 2, 3, 4, 5).pipe(
  tap(v => console.log('=5 Entrada:', v)),
  filter(x => x > 10), // L Todos son excluidos
  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:', result);
});

// Salida:
// =5 Entrada: 1
// =5 Entrada: 2
// =5 Entrada: 3
// =5 Entrada: 4
// =5 Entrada: 5
// (Ninguno pasó filter ’ filter es la causa)

Escenarios comunes de depuración ​

Escenario 1: Los valores no fluyen ​

Problema 1-1: Olvidaste subscribe ​

L Mal ejemplo: No hay subscribe ​

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

const result$ = of(1, 2, 3).pipe(
  map(x => x * 10)
);

console.log('Completado'); // Se imprime inmediatamente
// No fluyen valores a result$ (porque no hay subscribe)

 Buen ejemplo: Hacer subscribe ​

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

const result$ = of(1, 2, 3).pipe(
  map(x => x * 10)
);

result$.subscribe(value => {
  console.log('Valor:', value);
});

console.log('Completado');

// Salida:
// Valor: 10
// Valor: 20
// Valor: 30
// Completado

Puntos de verificación

  • Definir un Observable no hace nada
  • Debes hacer subscribe obligatoriamente
  • Si usas async pipe, subscribe no es necesario (Angular, etc.)

Problema 1-2: complete/error llega demasiado pronto ​

L Mal ejemplo: complete llega primero ​

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

EMPTY.pipe( // L complete inmediatamente
  map(x => x * 10)
).subscribe({
  next: value => console.log('Valor:', value),
  complete: () => console.log('Completado')
});

// Salida:
// Completado
// (No fluye ningún valor)

 Buen ejemplo: Verificar con tap ​

typescript
import { EMPTY } from 'rxjs';
import { map, tap } from 'rxjs';

EMPTY.pipe(
  tap(() => console.log('=A Llegó valor')), // Esto no se imprime
  map(x => x * 10)
).subscribe({
  next: value => console.log('Valor:', value),
  complete: () => console.log('Completado')
});

// Salida:
// Completado
// (tap tampoco se ejecuta ’ EMPTY es la causa)

Problema 1-3: Excluido por filter ​

L Mal ejemplo: Excluir todo sin darse cuenta ​

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

of(1, 2, 3, 4, 5).pipe(
  filter(x => x > 100) // L Todos excluidos
).subscribe(value => {
  console.log('Valor:', value); // No se imprime nada
});

 Buen ejemplo: Verificar con tap ​

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

of(1, 2, 3, 4, 5).pipe(
  tap(v => console.log('Antes de filter:', v)),
  filter(x => x > 100),
  tap(v => console.log('Después de filter:', v)) // No se imprime ninguno
).subscribe(value => {
  console.log('Valor:', value);
});

// Salida:
// Antes de filter: 1
// Antes de filter: 2
// Antes de filter: 3
// Antes de filter: 4
// Antes de filter: 5
// (No hay ninguno después de filter ’ filter es demasiado estricto)

Escenario 2: Aparecen valores diferentes a los esperados ​

Problema 2-1: Error de conversión de tipo ​

L Mal ejemplo: Confusión entre string y número ​

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

const input = '5'; // string

of(input).pipe(
  map(x => x + 10) // L '5' + 10 = '510' (concatenación de strings)
).subscribe(result => {
  console.log('Resultado:', result); // Resultado: 510
  console.log('Tipo:', typeof result); // Tipo: string
});

 Buen ejemplo: Verificar tipo con tap ​

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

const input = '5';

of(input).pipe(
  tap(x => console.log('Entrada:', x, typeof x)),
  map(x => Number(x)), // Convertir a número
  tap(x => console.log('Después de conversión:', x, typeof x)),
  map(x => x + 10)
).subscribe(result => {
  console.log('Resultado:', result); // Resultado: 15
});

Problema 2-2: Orden asíncrono ​

L Mal ejemplo: El 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() * 1000) // Retraso aleatorio
    )
  )
).subscribe(value => {
  console.log('Valor:', value);
});

// Ejemplo de salida (el orden no está garantizado):
// Valor: 20
// Valor: 10
// Valor: 30

 Buen ejemplo: Garantizar el orden con concatMap ​

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

of(1, 2, 3).pipe(
  tap(x => console.log('Entrada:', x)),
  concatMap(x =>
    of(x * 10).pipe(
      delay(Math.random() * 1000),
      tap(v => console.log('Completado:', v))
    )
  )
).subscribe(value => {
  console.log('Valor:', value);
});

// Salida (siempre en este orden):
// Entrada: 1
// Completado: 10
// Valor: 10
// Entrada: 2
// Completado: 20
// Valor: 20
// Entrada: 3
// Completado: 30
// Valor: 30

Escenario 3: Detección de fugas de memoria ​

Problema 3-1: Olvidaste unsubscribe ​

L Mal ejemplo: No hacer unsubscribe ​

typescript
import { interval } from 'rxjs';

class Component {
  ngOnInit() {
    interval(1000).subscribe(n => {
      console.log('Valor:', n); // Se ejecuta eternamente
    });
  }

  ngOnDestroy() {
    // No hay unsubscribe ’ Fuga de memoria
  }
}

 Buen ejemplo: Cancelación automática con takeUntil ​

typescript
import { interval, Subject } from 'rxjs';
import { takeUntil } from 'rxjs';

class Component {
  private destroy$ = new Subject<void>();

  ngOnInit() {
    interval(1000).pipe(
      takeUntil(this.destroy$)
    ).subscribe(n => {
      console.log('Valor:', n);
    });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
    console.log('Unsubscribe completado');
  }
}

Problema 3-2: Fuga de memoria con shareReplay ​

L Mal ejemplo: Fuga con refCount: false ​

typescript
import { interval } from 'rxjs';
import { shareReplay, take, tap } from 'rxjs';

const data$ = interval(1000).pipe(
  take(100),
  tap(n => console.log('Generando:', n)),
  shareReplay({ bufferSize: 1, refCount: false })
  // L refCount: false ’ Continúa ejecutándose eternamente
);

const sub = data$.subscribe(n => console.log('Suscripción 1:', n));

setTimeout(() => {
  sub.unsubscribe();
  console.log('Unsubscribe hecho, pero continúa ejecutándose internamente');
}, 5000);

 Buen ejemplo: Detención automática con refCount: true ​

typescript
import { interval } from 'rxjs';
import { shareReplay, take, tap } from 'rxjs';

const data$ = interval(1000).pipe(
  take(100),
  tap(n => console.log('Generando:', n)),
  shareReplay({ bufferSize: 1, refCount: true })
  //  refCount: true ’ Se detiene al cancelar todas las suscripciones
);

const sub = data$.subscribe(n => console.log('Suscripción 1:', n));

setTimeout(() => {
  sub.unsubscribe();
  console.log('Unsubscribe ’ El stream también se detiene');
}, 5000);

Herramientas y técnicas de depuración ​

1. Depuración gradual con tap ​

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

const debugTap = <T>(label: string, color: string = '=5') =>
  tap<T>({
    next: value => console.log(`${color} [${label}] next:`, value),
    error: error => console.error(`L [${label}] error:`, error),
    complete: () => console.log(` [${label}] complete`)
  });

of(1, 2, 3, 4, 5).pipe(
  debugTap('Entrada'),
  filter(x => x % 2 === 0),
  debugTap('Después de filter', '=â'),
  map(x => x * 10),
  debugTap('Después de map', '=á')
).subscribe({
  next: value => console.log('=æ Resultado final:', value),
  complete: () => console.log('<Á Completado')
});

2. Operador de depuración personalizado ​

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

interface DebugOptions {
  label: string;
  showTimestamp?: boolean;
  showDiff?: boolean;
}

let lastTimestamp = 0;

function debug<T>(options: DebugOptions): MonoTypeOperatorFunction<T> {
  const { label, showTimestamp = true, showDiff = true } = options;

  return source => source.pipe(
    timestamp(),
    tap(({ value, timestamp }) => {
      const parts = [`[${label}]`, value];

      if (showTimestamp) {
        parts.push(`@${new Date(timestamp).toISOString()}`);
      }

      if (showDiff && lastTimestamp > 0) {
        const diff = timestamp - lastTimestamp;
        parts.push(`(+${diff}ms)`);
      }

      console.log(...parts);
      lastTimestamp = timestamp;
    }),
    map(({ value }) => value)
  );
}

// Uso
import { interval } from 'rxjs';
import { take } from 'rxjs';

interval(500).pipe(
  take(5),
  debug({ label: 'Temporizador' }),
  map(x => x * 10),
  debug({ label: 'Después de transformación', showDiff: false })
).subscribe();

3. RxJS DevTools (extensión de navegador) ​

Método de instalación:

  1. Busca "RxJS DevTools" en Chrome/Edge Web Store
  2. Añade la extensión
  3. Abre DevTools y haz clic en la pestaña "RxJS"

Funciones principales:

  • Monitoreo en tiempo real de todos los Observables
  • Visualización con diagramas de mármol
  • Seguimiento de subscribe/unsubscribe
  • Análisis de rendimiento

Ejemplo de uso:

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

// Se detecta automáticamente en DevTools
const timer$ = interval(1000).pipe(
  take(10),
  map(x => x * 2)
);

timer$.subscribe(value => console.log(value));

4. Depuración de errores ​

Identificar dónde ocurre el error ​

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

of(1, 2, 3).pipe(
  tap(v => console.log('1. Entrada:', v)),
  map(x => {
    if (x === 2) {
      throw new Error('No se puede usar 2');
    }
    return x * 10;
  }),
  tap(v => console.log('2. Después de map:', v)), // No se ejecuta en caso de error
  catchError(error => {
    console.error('3. Error capturado:', error.message);
    return of(-1); // Devolver valor por defecto
  }),
  tap(v => console.log('4. Después de catchError:', v))
).subscribe({
  next: value => console.log('5. Resultado:', value),
  error: error => console.error('Error de suscripción:', error),
  complete: () => console.log('6. Completado')
});

// Salida:
// 1. Entrada: 1
// 2. Después de map: 10
// 5. Resultado: 10
// 1. Entrada: 2
// 3. Error capturado: No se puede usar 2
// 4. Después de catchError: -1
// 5. Resultado: -1
// 6. Completado

Depuración de rendimiento ​

Problema 1: Recalculación excesiva ​

L Mal ejemplo: Recalculación frecuente con combineLatest ​

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs';

const a$ = new BehaviorSubject(1);
const b$ = new BehaviorSubject(2);
const c$ = new BehaviorSubject(3);

combineLatest([a$, b$, c$]).pipe(
  map(([a, b, c]) => {
    console.log('Ejecutando cálculo pesado'); // Se ejecuta frecuentemente
    return a + b + c;
  })
).subscribe(result => console.log('Resultado:', result));

// Actualización frecuente
setInterval(() => {
  a$.next(Math.random());
}, 100);

 Buen ejemplo: Excluir duplicados con distinctUntilChanged ​

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map, distinctUntilChanged } from 'rxjs';

const a$ = new BehaviorSubject(1);
const b$ = new BehaviorSubject(2);
const c$ = new BehaviorSubject(3);

combineLatest([a$, b$, c$]).pipe(
  map(([a, b, c]) => Math.floor(a) + Math.floor(b) + Math.floor(c)),
  distinctUntilChanged(), // Pasa solo cuando el valor cambia
  map(sum => {
    console.log('Ejecutando cálculo pesado'); // Solo cuando el valor cambia
    return sum * 2;
  })
).subscribe(result => console.log('Resultado:', result));

setInterval(() => {
  a$.next(Math.random());
}, 100);

Problema 2: Monitoreo del uso de memoria ​

typescript
import { interval } from 'rxjs';
import { scan, tap } from 'rxjs';

let itemCount = 0;

interval(100).pipe(
  scan((acc, val) => {
    acc.push(val);
    itemCount = acc.length;
    return acc;
  }, [] as number[]),
  tap(() => {
    if (itemCount % 100 === 0) {
      console.log(`Número de elementos: ${itemCount}`);
      if (itemCount > 10000) {
        console.warn('  Uso de memoria demasiado alto');
      }
    }
  })
).subscribe();

Problema 3: Monitoreo del número de suscripciones ​

typescript
import { Observable, Subject } from 'rxjs';

class MonitoredSubject<T> extends Subject<T> {
  private subscriptionCount = 0;

  subscribe(...args: any[]): any {
    this.subscriptionCount++;
    console.log(`Número de suscripciones: ${this.subscriptionCount}`);

    const subscription = super.subscribe(...args);

    const originalUnsubscribe = subscription.unsubscribe.bind(subscription);
    subscription.unsubscribe = () => {
      this.subscriptionCount--;
      console.log(`Número de suscripciones: ${this.subscriptionCount}`);
      originalUnsubscribe();
    };

    return subscription;
  }
}

// Uso
const data$ = new MonitoredSubject<number>();

const sub1 = data$.subscribe(v => console.log('Suscripción 1:', v));
const sub2 = data$.subscribe(v => console.log('Suscripción 2:', v));

sub1.unsubscribe();
sub2.unsubscribe();

// Salida:
// Número de suscripciones: 1
// Número de suscripciones: 2
// Número de suscripciones: 1
// Número de suscripciones: 0

Lista de verificación de depuración ​

Si surge un problema, verifica lo siguiente en orden.

markdown
## Verificación básica
- [ ] ¿Estás llamando a `subscribe()`?
- [ ] ¿`complete` o `error` no llegan demasiado pronto?
- [ ] ¿Los valores no están siendo excluidos por `filter` o `take`?
- [ ] ¿Estás esperando la finalización del procesamiento asíncrono?

## Verificación de timing
- [ ] ¿Entiendes sincronía/asincronía?
- [ ] ¿Verificaste el impacto de `delay`, `debounceTime`, `throttleTime`?
- [ ] ¿Se cumple la condición de primera emisión de `combineLatest`?

## Verificación de memoria
- [ ] ¿Estás usando `unsubscribe` o `takeUntil`?
- [ ] ¿Configuraste `refCount: true` en `shareReplay`?
- [ ] ¿Estás delimitando adecuadamente los Observables infinitos?

## Verificación de rendimiento
- [ ] ¿No hay recalculación excesiva? (considera `distinctUntilChanged`)
- [ ] ¿El número de suscripciones no aumenta demasiado?
- [ ] ¿Asincronizan los procesos pesados con `observeOn(asyncScheduler)`?

Lista de verificación de comprensión ​

Verifica si puedes responder a las siguientes preguntas.

markdown
## Depuración básica
- [ ] Puedo depurar el flujo de valores usando tap
- [ ] Puedo identificar dónde ocurren los errores
- [ ] Puedo verificar el timing de complete/error

## Uso de herramientas
- [ ] Conozco el uso básico de RxJS DevTools
- [ ] Puedo crear operadores de depuración personalizados
- [ ] Puedo medir el timing usando timestamp

## Resolución de problemas
- [ ] Puedo identificar la causa cuando no fluyen valores
- [ ] Puedo encontrar señales de fugas de memoria
- [ ] Puedo identificar problemas de rendimiento

## Prevención
- [ ] Tengo el hábito de depuración gradual usando tap
- [ ] Implemento manejo de errores adecuadamente
- [ ] Conozco medidas contra fugas de memoria

Próximos pasos ​

Una vez que entiendas las técnicas de depuración, integra todo el conocimiento aprendido hasta ahora y aprende patrones prácticos.

’ Chapter 13: Colección de patrones prácticos (en preparación) - Colección de patrones utilizables en el trabajo

Páginas relacionadas ​

<¯ Ejercicios de práctica ​

Problema 1: Identificar la causa cuando no fluyen valores ​

En el siguiente código, identifica por qué no se imprimen valores.

typescript
import { Subject, combineLatest } from 'rxjs';

const a$ = new Subject<number>();
const b$ = new Subject<number>();

combineLatest([a$, b$]).subscribe(([a, b]) => {
  console.log('Valores:', a, b);
});

a$.next(1);
console.log('Completado');
Respuesta

Causa

combineLatest no emite hasta que todos los streams emitan al menos un valor

Como b$ aún no ha emitido un valor, solo a$.next(1) no provoca la emisión.

Método de corrección 1: Emitir valor también en b$ ​

typescript
import { Subject, combineLatest } from 'rxjs';

const a$ = new Subject<number>();
const b$ = new Subject<number>();

combineLatest([a$, b$]).subscribe(([a, b]) => {
  console.log('Valores:', a, b);
});

a$.next(1);
b$.next(2); //  Aquí se emite
console.log('Completado');

// Salida:
// Valores: 1 2
// Completado

Método de corrección 2: Usar BehaviorSubject ​

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';

const a$ = new BehaviorSubject<number>(0); // Valor inicial
const b$ = new BehaviorSubject<number>(0);

combineLatest([a$, b$]).subscribe(([a, b]) => {
  console.log('Valores:', a, b);
});

// Salida: Valores: 0 0 (emite inmediatamente)

a$.next(1);
// Salida: Valores: 1 0

Técnica de depuración

Usando tap para verificar los valores de cada stream, puedes saber dónde se detiene.

typescript
a$.pipe(tap(v => console.log('a$:', v)))
b$.pipe(tap(v => console.log('b$:', v)))

Problema 2: Corregir la fuga de memoria ​

El siguiente código tiene una fuga de memoria. Corrígelo.

typescript
import { interval } from 'rxjs';
import { Component } from '@angular/core';

class MyComponent implements Component {
  ngOnInit() {
    interval(1000).subscribe(n => {
      console.log('Temporizador:', n);
    });
  }

  ngOnDestroy() {
    console.log('Destruido');
  }
}
Ejemplo de respuesta

Problema

Al no hacer unsubscribe en ngOnDestroy, interval continúa ejecutándose incluso después de destruir el componente**

Método de corrección 1: Guardar Subscription y hacer unsubscribe ​

typescript
import { interval, Subscription } from 'rxjs';

class MyComponent {
  private subscription!: Subscription;

  ngOnInit() {
    this.subscription = interval(1000).subscribe(n => {
      console.log('Temporizador:', n);
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
    console.log('Destruido y unsubscribe');
  }
}

Método de corrección 2: Usar takeUntil (recomendado) ​

typescript
import { interval, Subject } from 'rxjs';
import { takeUntil } from 'rxjs';

class MyComponent {
  private destroy$ = new Subject<void>();

  ngOnInit() {
    interval(1000).pipe(
      takeUntil(this.destroy$)
    ).subscribe(n => {
      console.log('Temporizador:', n);
    });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
    console.log('Destruido y unsubscribe');
  }
}

Puntos

  • Los Observables infinitos como interval requieren unsubscribe obligatoriamente
  • Se recomienda el patrón takeUntil (permite gestionar múltiples suscripciones de forma unificada)
  • En Angular, si usas async pipe, unsubscribe se hace automáticamente

Problema 3: Problema de orden ​

En el siguiente código, explica por qué el orden no está garantizado y corrígelo.

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

from([1, 2, 3]).pipe(
  mergeMap(x =>
    of(x).pipe(
      delay(Math.random() * 1000)
    )
  )
).subscribe(value => console.log(value));

// Ejemplo de salida: 2, 1, 3 (el orden no está garantizado)
Respuesta

Problema

mergeMap ejecuta en paralelo, por lo que el orden de finalización depende del tiempo de ejecución**

Método de corrección: Usar concatMap ​

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

from([1, 2, 3]).pipe(
  tap(x => console.log('Inicio:', x)),
  concatMap(x =>
    of(x).pipe(
      delay(Math.random() * 1000),
      tap(v => console.log('Completado:', v))
    )
  )
).subscribe(value => console.log('Resultado:', value));

// Salida (siempre en este orden):
// Inicio: 1
// Completado: 1
// Resultado: 1
// Inicio: 2
// Completado: 2
// Resultado: 2
// Inicio: 3
// Completado: 3
// Resultado: 3

Razón

  • mergeMap: Ejecución paralela, el orden de finalización no está garantizado
  • concatMap: Ejecución secuencial, siempre emite en el mismo orden que la entrada
  • switchMap: Solo el más reciente, procesos antiguos se cancelan
  • exhaustMap: Ignora nuevos procesos durante la ejecución

Comparación con diagramas de mármol ​

Entrada:  --1--2--3----|

mergeMap: --2--1--3--|  (orden de finalización)
concatMap: --1--2--3-| (orden de entrada)

Problema 4: Mejora de rendimiento ​

El siguiente código recalcula frecuentemente. Mejora el rendimiento.

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

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

fromEvent(input, 'input').pipe(
  map(e => (e.target as HTMLInputElement).value),
  map(value => {
    console.log('Ejecutando cálculo pesado');
    return value.toUpperCase();
  })
).subscribe(result => console.log(result));

// Usuario escribe "hello"
// Ejecutando cálculo pesado (h)
// Ejecutando cálculo pesado (he)
// Ejecutando cálculo pesado (hel)
// Ejecutando cálculo pesado (hell)
// Ejecutando cálculo pesado (hello)
Respuesta

Método de mejora 1: Esperar finalización de entrada con debounceTime ​

typescript
import { fromEvent } from 'rxjs';
import { map, debounceTime } from 'rxjs';

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

fromEvent(input, 'input').pipe(
  debounceTime(300), // Ejecuta si no hay entrada durante 300ms
  map(e => (e.target as HTMLInputElement).value),
  map(value => {
    console.log('Ejecutando cálculo pesado');
    return value.toUpperCase();
  })
).subscribe(result => console.log(result));

// Se ejecuta solo una vez después de escribir "hello" y esperar 300ms

Método de mejora 2: Excluir duplicados con distinctUntilChanged ​

typescript
import { fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs';

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

fromEvent(input, 'input').pipe(
  debounceTime(300),
  map(e => (e.target as HTMLInputElement).value),
  distinctUntilChanged(), // Ignorar si es el mismo valor que la vez anterior
  map(value => {
    console.log('Ejecutando cálculo pesado');
    return value.toUpperCase();
  })
).subscribe(result => console.log(result));

Técnicas de mejora de rendimiento

  • debounceTime: Esperar finalización de entrada
  • throttleTime: Reducir a intervalos regulares
  • distinctUntilChanged: Excluir duplicados
  • observeOn(asyncScheduler): Asincronizar procesos pesados
  • shareReplay: Cachear resultados

Publicado bajo licencia CC-BY-4.0.