Skip to content

La confusi�n de selecci�n de operadores

RxJS tiene m�s de 100 tipos de operadores, y dudar sobre cu�l usar es una dificultad que todos experimentan. Esta p�gina proporciona criterios de selecci�n pr�cticos y diagramas de flujo.

Criterios para elegir entre m�s de 100 operadores

Problema: Demasiadas opciones

typescript
// Quiero transformar un array... �map? �scan? �reduce? �toArray?
// Quiero llamar m�ltiples APIs... �mergeMap? �switchMap? �concatMap? �exhaustMap?
// Quiero filtrar valores... �filter? �take? �first? �distinctUntilChanged?
// Quiero combinar m�ltiples streams... �merge? �combineLatest? �zip? �forkJoin?

Soluci�n: Acotar por categor�a + prop�sito

Diagrama de flujo de selecci�n m�s detallado

El siguiente diagrama de flujo muestra el procedimiento para elegir operadores seg�n prop�sitos espec�ficos.

1. Operadores de transformaci�n (Transformation)

�Cu�ndo usar? Cuando quieres cambiar la forma de los datos, llamar procesamiento as�ncrono

OperadorProp�sitoCasos de uso comunes
mapTransformaci�n 1:1 de valoresObtener propiedades, c�lculos, conversi�n de tipos
scanProcesamiento acumulativo (fluye valores intermedios)Contador, suma, historial
reduceProcesamiento acumulativo (solo valor final)Suma de array, valor m�ximo
mergeMapEjecuci�n paralela de procesamiento as�ncronoLlamadas paralelas a m�ltiples APIs
switchMapCambiar procesamiento as�ncronoAPI de b�squeda (solo el m�s reciente)
concatMapEjecuci�n secuencial de procesamiento as�ncronoProcesamiento donde el orden es importante
exhaustMapIgnorar nuevo procesamiento durante ejecuci�nPrevenci�n de clics m�ltiples (bot�n enviar)

Ejemplo pr�ctico: Selecci�n por caso de uso

Caso de uso 1: Obtener propiedad

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

interface User { id: number; name: string; }

of({ id: 1, name: 'Alice' }).pipe(
  map(user => user.name) // Transformaci�n 1:1 de valor � map
).subscribe(name => console.log(name)); // 'Alice'

Caso de uso 2: Contador

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

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

fromEvent(button, 'click').pipe(
  scan(count => count + 1, 0) // Procesamiento acumulativo � scan
).subscribe(count => console.log(`N�mero de clics: ${count}`));

Caso de uso 3: Llamada a API de b�squeda

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

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

fromEvent(searchInput, 'input').pipe(
  debounceTime(300),
  map(e => (e.target as HTMLInputElement).value),
  switchMap(query => searchAPI(query)) // Solo el m�s reciente � switchMap
).subscribe(results => console.log(results));

2. Operadores de filtrado (Filtering)

�Cu�ndo usar?

Cuando quieres seleccionar valores, controlar el timing

OperadorProp�sitoCasos de uso comunes
filterPasar solo valores que cumplen condici�nSolo n�meros pares, solo valores no nulos
takeSolo los primeros NObtener primeros 5 elementos
firstSolo el primeroObtener valor inicial
distinctUntilChangedSolo valores diferentes del anteriorExcluir duplicados
debounceTimeEmitir despu�s de tiempo transcurridoEntrada de b�squeda (despu�s de completar entrada)
throttleTimeReducir a intervalos regularesEvento de scroll

Ejemplo pr�ctico: Selecci�n por caso de uso

Caso de uso 1: Obtener solo n�meros pares

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

of(1, 2, 3, 4, 5).pipe(
  filter(n => n % 2 === 0) // Solo valores que cumplen condici�n � filter
).subscribe(console.log); // 2, 4

Caso de uso 2: Optimizaci�n de entrada de b�squeda

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

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

fromEvent(input, 'input').pipe(
  debounceTime(300),              // Esperar finalizaci�n de entrada � debounceTime
  map(e => (e.target as HTMLInputElement).value),
  distinctUntilChanged()          // Excluir duplicados � distinctUntilChanged
).subscribe(query => console.log('B�squeda:', query));

Caso de uso 3: Reducci�n de eventos de scroll

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

fromEvent(window, 'scroll').pipe(
  throttleTime(200) // Solo una vez cada 200ms � throttleTime
).subscribe(() => console.log('Posici�n de scroll:', window.scrollY));

3. Operadores de combinaci�n (Combination)

�Cu�ndo usar?

Cuando quieres combinar m�ltiples streams

OperadorProp�sitoCasos de uso comunes
mergeM�ltiples streams en paraleloMonitoreo de m�ltiples eventos
combineLatestCombinar todos los valores m�s recientesValidaci�n de formularios
zipEmparejar valores correspondientesRelacionar resultados de 2 APIs
forkJoinResultados en array despu�s de finalizaci�n completaEjecuci�n paralela de m�ltiples APIs
withLatestFromStream principal + valor auxiliarEvento + estado actual

Ejemplo pr�ctico: Selecci�n por caso de uso

Caso de uso 1: Monitorear m�ltiples eventos

typescript
import { fromEvent, merge } from 'rxjs';

const clicks$ = fromEvent(document, 'click');
const keypresses$ = fromEvent(document, 'keypress');

merge(clicks$, keypresses$).pipe() // Monitoreo paralelo � merge
  .subscribe(() => console.log('Ocurri� alg�n evento'));

Caso de uso 2: Validaci�n de formularios

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

const email$ = getFormControl('email');
const password$ = getFormControl('password');

combineLatest([email$, password$]).pipe( // Todos los valores m�s recientes � combineLatest
  map(([email, password]) => email.length > 0 && password.length > 7)
).subscribe(isValid => console.log('Formulario v�lido:', isValid));

Caso de uso 3: Ejecuci�n paralela de m�ltiples APIs

typescript
import { forkJoin } from 'rxjs';

forkJoin({
  user: getUserAPI(),
  posts: getPostsAPI(),
  comments: getCommentsAPI()
}).subscribe(({ user, posts, comments }) => { // Espera finalizaci�n completa � forkJoin
  console.log('Obtenci�n completa de todos los datos', { user, posts, comments });
});

Los 20 operadores m�s usados

Los siguientes son los operadores m�s utilizados frecuentemente en el trabajo. Primero domina estos 20.

>G M�s frecuentes (obligatorios)

  1. map - Transformar valores
  2. filter - Filtrar por condici�n
  3. switchMap - B�squeda, etc., solo necesario el m�s reciente
  4. tap - Depuraci�n, efectos secundarios
  5. take - Primeros N
  6. first - Primero 1
  7. catchError - Manejo de errores
  8. takeUntil - Cancelar suscripci�n

>H Frecuentes (uso com�n)

  1. mergeMap - Procesamiento as�ncrono paralelo
  2. debounceTime - Esperar finalizaci�n de entrada
  3. distinctUntilChanged - Excluir duplicados
  4. combineLatest - Combinar m�ltiples valores
  5. startWith - Establecer valor inicial
  6. scan - Procesamiento acumulativo
  7. shareReplay - Cachear resultados

>I Uso com�n (deber�as conocer)

  1. concatMap - Procesamiento secuencial
  2. throttleTime - Reducci�n de eventos
  3. withLatestFrom - Obtener valor auxiliar
  4. forkJoin - Espera de m�ltiples APIs
  5. retry - Procesamiento de reintento

switchMap vs mergeMap vs concatMap vs exhaustMap

Estos 4 son los operadores m�s confundidos. Entendamos claramente sus diferencias.

Tabla comparativa

OperadorM�todo de ejecuci�nProcesamiento anteriorNuevo procesamientoD�nde usar
switchMapCambiarCancelarIniciar inmediatamenteB�squeda, autocompletar
mergeMapEjecuci�n paralelaContinuarIniciar inmediatamenteSubida de archivos, an�lisis
concatMapEjecuci�n secuencialEsperar finalizaci�nIniciar despu�s de esperarProcesamiento donde el orden es importante
exhaustMapIgnorar durante ejecuci�nContinuarIgnorarPrevenci�n de clics m�ltiples en bot�n

Comparaci�n con diagramas de m�rmol

Exterior: ----A----B----C----|

Interior: A � --1--2|
      B � --3--4|
      C � --5--6|

switchMap:  ----1--3--5--6|  (A se cancela antes de 2, B se cancela antes de 4)
mergeMap:   ----1-23-45-6|   (todo ejecuci�n paralela)
concatMap:  ----1--2--3--4--5--6|  (ejecuci�n secuencial)
exhaustMap: ----1--2|            (B, C se ignoran)

Ejemplo pr�ctico: Diferencia de los 4 en el mismo procesamiento

Situaci�n: Llamar API (tarda 1 segundo) con cada clic del bot�n. Usuario hace clic cada 0.5 segundos.

switchMap - �ptimo para b�squeda

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

fromEvent(button, 'click').pipe(
  switchMap(() => searchAPI()) // Solo ejecutar el m�s reciente, cancelar solicitudes antiguas
).subscribe(result => console.log(result));

// 0.0 seg: Clic1 � Inicio API1
// 0.5 seg: Clic2 � Cancelar API1, Inicio API2
// 1.0 seg: Clic3 � Cancelar API2, Inicio API3
// 2.0 seg: Completar API3 � Mostrar resultado (solo API3)

=� D�nde usar

  • B�squeda/autocompletar: Solo necesario el valor de entrada m�s reciente
  • Cambio de pesta�as: Solo necesarios datos de la pesta�a mostrada
  • Paginaci�n: Solo mostrar la p�gina m�s reciente

mergeMap - �ptimo para procesamiento paralelo

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

fromEvent(button, 'click').pipe(
  mergeMap(() => uploadFileAPI()) // Todo ejecuci�n paralela
).subscribe(result => console.log(result));

// 0.0 seg: Clic1 � Inicio API1
// 0.5 seg: Clic2 � Inicio API2 (API1 contin�a)
// 1.0 seg: Clic3 � Inicio API3 (API1, API2 contin�an)
// 1.0 seg: Completar API1 � Mostrar resultado
// 1.5 seg: Completar API2 � Mostrar resultado
// 2.0 seg: Completar API3 � Mostrar resultado

=� D�nde usar

  • Subida de archivos: Subir m�ltiples archivos simult�neamente
  • An�lisis/env�o de logs: Ejecutar procesamientos independientes en paralelo
  • Sistema de notificaciones: Procesar m�ltiples notificaciones simult�neamente

concatMap - �ptimo para procesamiento donde el orden es importante

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

fromEvent(button, 'click').pipe(
  concatMap(() => updateDatabaseAPI()) // Ejecuci�n secuencial (esperar finalizaci�n anterior)
).subscribe(result => console.log(result));

// 0.0 seg: Clic1 � Inicio API1
// 0.5 seg: Clic2 � Esperar (a�adir a cola)
// 1.0 seg: Clic3 � Esperar (a�adir a cola)
// 1.0 seg: Completar API1 � Mostrar resultado, Inicio API2
// 2.0 seg: Completar API2 � Mostrar resultado, Inicio API3
// 3.0 seg: Completar API3 � Mostrar resultado

=� D�nde usar

  • Actualizaci�n de base de datos: Procesamiento de escritura donde el orden es importante
  • Transacciones: Usar resultado del procesamiento anterior en el siguiente
  • Animaciones: Procesos que quieres ejecutar en orden

exhaustMap - �ptimo para prevenci�n de clics m�ltiples

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

fromEvent(button, 'click').pipe(
  exhaustMap(() => submitFormAPI()) // Ignorar nuevas solicitudes durante ejecuci�n
).subscribe(result => console.log(result));

// 0.0 seg: Clic1 � Inicio API1
// 0.5 seg: Clic2 � Ignorar (API1 en ejecuci�n)
// 1.0 seg: Clic3 � Ignorar (API1 en ejecuci�n)
// 1.0 seg: Completar API1 � Mostrar resultado
// 1.5 seg: Clic4 � Inicio API4 (anterior ya completado)

=� D�nde usar

  • Bot�n enviar: Prevenci�n de env�o doble
  • Procesamiento de login: Prevenir errores por clics m�ltiples
  • Procesamiento de pago: Prevenir ejecuci�n duplicada

Diagrama de flujo de selecci�n

Criterios de decisi�n en la pr�ctica

Paso 1: Clarificar qu� quieres lograr

typescript
// L Mal ejemplo: Usar mergeMap sin m�s
observable$.pipe(
  mergeMap(value => someAPI(value))
);

//  Buen ejemplo: Elegir despu�s de clarificar el prop�sito
// Prop�sito: Para la entrada de b�squeda del usuario, quiero mostrar solo el resultado m�s reciente
// � Deber�a cancelar solicitudes antiguas � switchMap
searchInput$.pipe(
  switchMap(query => searchAPI(query))
);

Paso 2: Considerar el rendimiento

Elecci�n de debounceTime vs throttleTime

typescript
// Entrada de b�squeda: Ejecutar despu�s de que el usuario "complete" la entrada
searchInput$.pipe(
  debounceTime(300), // Ejecutar si no hay entrada durante 300ms
  switchMap(query => searchAPI(query))
);

// Scroll: Ejecutar a intervalos regulares (prevenir alta frecuencia)
scroll$.pipe(
  throttleTime(200), // Solo ejecutar una vez cada 200ms
  tap(() => loadMoreItems())
);

Paso 3: Incorporar manejo de errores

typescript
import { of } from 'rxjs';
import { catchError, retry, switchMap } from 'rxjs';

searchInput$.pipe(
  debounceTime(300),
  switchMap(query =>
    searchAPI(query).pipe(
      retry(2),                          // Reintentar hasta 2 veces
      catchError(err => {
        console.error('Error de b�squeda:', err);
        return of([]);                   // Devolver array vac�o
      })
    )
  )
).subscribe(results => console.log(results));

Paso 4: Prevenir fugas de memoria

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

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

  ngOnInit() {
    searchInput$.pipe(
      debounceTime(300),
      switchMap(query => searchAPI(query)),
      takeUntil(this.destroy$)           // Cancelar al destruir componente
    ).subscribe(results => console.log(results));
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Lista de verificaci�n de comprensi�n

Verifica si puedes responder a las siguientes preguntas.

markdown
## Comprensi�n b�sica
- [ ] Puedo clasificar operadores por categor�a (transformaci�n, filtrado, combinaci�n)
- [ ] Puedo explicar m�s de 10 de los 20 operadores m�s usados
- [ ] Puedo explicar las diferencias entre switchMap, mergeMap, concatMap, exhaustMap

## Selecci�n pr�ctica
- [ ] Puedo elegir operadores adecuados para funci�n de b�squeda (switchMap + debounceTime)
- [ ] Puedo elegir operadores adecuados para llamadas paralelas a m�ltiples APIs (forkJoin or mergeMap)
- [ ] Puedo elegir operadores adecuados para validaci�n de formularios (combineLatest)

## Rendimiento
- [ ] Puedo diferenciar el uso de debounceTime y throttleTime
- [ ] Conozco m�todos de optimizaci�n para eventos de alta frecuencia
- [ ] Puedo implementar patrones para prevenir fugas de memoria

## Manejo de errores
- [ ] Puedo usar catchError y retry en combinaci�n
- [ ] Puedo implementar procesamiento de fallback en caso de error
- [ ] Puedo dar feedback de errores al usuario

Pr�ximos pasos

Una vez que entiendas la selecci�n de operadores, aprende sobre timing y orden.

Comprensi�n de timing y orden (en preparaci�n) - Cu�ndo fluyen los valores, comprensi�n de sincron�a vs asincron�a

P�ginas relacionadas

<� Ejercicios de pr�ctica

Problema 1: Seleccionar operador adecuado

Elige el operador m�s �ptimo para los siguientes escenarios.

  1. Usuario ingresa en caja de b�squeda � Llamada a API
  2. Clic de bot�n para subir m�ltiples archivos
  3. Determinar si todos los campos del formulario son v�lidos
  4. Prevenir clics m�ltiples en bot�n enviar
Ejemplo de respuesta

1. Caja de b�squeda � Llamada a API

typescript
searchInput$.pipe(
  debounceTime(300),      // Esperar finalizaci�n de entrada
  distinctUntilChanged(), // Excluir duplicados
  switchMap(query => searchAPI(query)) // Solo el m�s reciente
).subscribe(results => displayResults(results));

Raz�n

La b�squeda solo necesita el resultado m�s reciente, por lo que switchMap. Espera finalizaci�n de entrada con debounceTime.


2. Subir m�ltiples archivos

typescript
fromEvent(uploadButton, 'click').pipe(
  mergeMap(() => {
    const files = getSelectedFiles();
    return forkJoin(files.map(file => uploadFileAPI(file)));
  })
).subscribe(results => console.log('Subida completa de todos los archivos', results));

Raz�n

Para subir m�ltiples archivos en paralelo, forkJoin. Tambi�n es posible mergeMap para procesamientos independientes.


3. Validez de todos los campos del formulario

typescript
combineLatest([
  emailField$,
  passwordField$,
  agreeTerms$
]).pipe(
  map(([email, password, agreed]) =>
    email.valid && password.valid && agreed
  )
).subscribe(isValid => submitButton.disabled = !isValid);

Raz�n

Para combinar los valores m�s recientes de todos los campos, combineLatest.


4. Prevenci�n de clics m�ltiples en bot�n enviar

typescript
fromEvent(submitButton, 'click').pipe(
  exhaustMap(() => submitFormAPI())
).subscribe(result => console.log('Env�o completado', result));

Raz�n

Para proteger el procesamiento en ejecuci�n e ignorar nuevos clics, exhaustMap.

Problema 2: Elecci�n de switchMap y mergeMap

El siguiente c�digo usa mergeMap, pero hay un problema. Corr�gelo.

typescript
searchInput$.pipe(
  debounceTime(300),
  mergeMap(query => searchAPI(query))
).subscribe(results => displayResults(results));
Ejemplo de respuesta
typescript
searchInput$.pipe(
  debounceTime(300),
  switchMap(query => searchAPI(query)) // mergeMap � switchMap
).subscribe(results => displayResults(results));

Problema

  • Con mergeMap, todas las solicitudes de b�squeda se ejecutan en paralelo
  • Si el usuario ingresa "a"�"ab"�"abc", se ejecutan las 3 solicitudes
  • Solicitudes antiguas (resultado de "a") pueden regresar despu�s y sobrescribir el resultado m�s reciente

Raz�n de correcci�n

  • Usando switchMap, cuando comienza una nueva b�squeda, se cancelan las solicitudes antiguas
  • Siempre se muestra solo el resultado de b�squeda m�s reciente

Problema 3: Escenario pr�ctico

Escribe c�digo que cumpla los siguientes requisitos.

Puntos clave

  • Usuario hace clic en bot�n
  • Obtener en paralelo 3 APIs (informaci�n de usuario, lista de posts, lista de comentarios)
  • Mostrar datos cuando todo est� completo
  • Si ocurre error, devolver datos vac�os
  • Cancelar suscripci�n al destruir componente
Ejemplo de respuesta
typescript
import { fromEvent, forkJoin, of, Subject } from 'rxjs';
import { switchMap, catchError, takeUntil } from 'rxjs';

class DataComponent {
  private destroy$ = new Subject<void>();
  private button = document.querySelector('button')!;

  ngOnInit() {
    fromEvent(this.button, 'click').pipe(
      switchMap(() =>
        forkJoin({
          user: this.getUserAPI().pipe(
            catchError(() => of(null))
          ),
          posts: this.getPostsAPI().pipe(
            catchError(() => of([]))
          ),
          comments: this.getCommentsAPI().pipe(
            catchError(() => of([]))
          )
        })
      ),
      takeUntil(this.destroy$)
    ).subscribe(({ user, posts, comments }) => {
      console.log('Obtenci�n de datos completada', { user, posts, comments });
    });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  private getUserAPI() { /* ... */ }
  private getPostsAPI() { /* ... */ }
  private getCommentsAPI() { /* ... */ }
}

Puntos

  • forkJoin ejecuta 3 APIs en paralelo y espera finalizaci�n completa
  • Establecer valor de fallback en caso de error con catchError en cada API
  • switchMap cambia a nueva solicitud con cada clic del bot�n
  • Cancelaci�n autom�tica al destruir componente con takeUntil

Publicado bajo licencia CC-BY-4.0.