RxJs: Peticiones HTTP/AJAX con rxjs/ajax y catchError

RxJs: Peticiones HTTP/AJAX con rxjs/ajax y catchError

Tabla de Contenido

En aplicaciones frontend, las peticiones ajax rxjs resultan útiles cuando el tráfico HTTP no es un evento aislado, sino parte de un flujo: búsquedas con cancelación, reintentos, composición con eventos de UI o coordinación entre varias fuentes asíncronas. fetch cubre bien el caso imperativo basado en promesas, pero rxjs/ajax integra las peticiones HTTP directamente con observables, operadores y suscripciones.

Note

Este artículo forma parte de la serie RxJs. Venimos de analizar los operadores de combinación y cerraremos la serie abordando rxjs/ajax, ajax.getJSON, métodos HTTP y catchError rxjs para manejar fallos sin romper el flujo.

fetch frente a rxjs/ajax

fetch es el estándar moderno del navegador para ejecutar peticiones HTTP. Su API está basada en Promise, por lo que encaja bien cuando necesitas una única respuesta y un flujo de control lineal.

const url = 'https://api.github.com/users?per_page=5';

const manejaErrores = (response: Response) => {
  if (!response.ok) {
    throw new Error(response.statusText);
  }

  return response;
};

fetch(url)
  .then(manejaErrores)
  .then((response) => response.json())
  .then((data) => console.log('data', data))
  .catch((error) => console.warn('Error en usuarios', error));

La limitación aparece cuando la petición debe integrarse con otros streams. Una Promise no tiene el concepto de suscripción, no emite múltiples valores y no se cancela por sí misma al dejar de interesarte el resultado. fetch puede abortarse con AbortController, pero esa cancelación queda como una responsabilidad externa que debes coordinar manualmente.

Con RxJS, en cambio, la suscripción representa el ciclo de vida del trabajo asíncrono. En rxjs/ajax, cancelar la suscripción aborta la petición subyacente basada en XMLHttpRequest, lo cual encaja naturalmente con operadores como switchMap, takeUntil o debounceTime.

Info

Usa fetch para flujos simples basados en una sola respuesta. Usa rxjs/ajax cuando la petición forma parte de una arquitectura reactiva y necesitas composición, cancelación por suscripción o manejo declarativo de errores.

rxjs ajax para peticiones HTTP

El módulo rxjs/ajax expone la función ajax, que devuelve un observable. Al suscribirte, se ejecuta la petición; al desuscribirte, se cancela si todavía está pendiente.

import { ajax } from 'rxjs/ajax';
import { map } from 'rxjs';

interface GitHubUser {
  id: number;
  login: string;
  html_url: string;
}

const url = 'https://api.github.com/users?per_page=5';

ajax<GitHubUser[]>(url)
  .pipe(map((ajaxResponse) => ajaxResponse.response))
  .subscribe({
    next: (users) => console.log('usuarios', users),
    error: (error) => console.warn('Error en usuarios', error),
  });

El observable emitido por ajax no entrega únicamente el cuerpo. Entrega un AjaxResponse<T> con metadatos como status, response, request y xhr. Esto es útil cuando la lógica depende del código HTTP, encabezados o detalles del transporte.

Para obtener solo el cuerpo, el patrón habitual consiste en aplicar map sobre response. En versiones modernas de RxJS, importar operadores desde rxjs mantiene el código conciso y compatible con el estilo recomendado.

catchError rxjs: recuperar el stream ante fallos

El operador catchError intercepta errores dentro de un observable y debe retornar un nuevo observable o un valor compatible con ObservableInput. Esto permite reemplazar el flujo fallido por un valor seguro, reintentar con otra fuente o propagar un error enriquecido.

Imagen guia del contenido de la página

El diagrama muestra el punto clave: cuando ocurre un error, catchError reemplaza el stream original por otro observable. Si no retornas un observable, la cadena queda en un estado inválido.

import { of } from 'rxjs';
import { ajax, AjaxError } from 'rxjs/ajax';
import { catchError, map } from 'rxjs';

interface GitHubUser {
  id: number;
  login: string;
}

const url = 'https://api.github.com/users?per_page=5';

const atrapaError = (error: AjaxError) => {
  console.warn('Error:', error.message);
  return of([] as GitHubUser[]);
};

ajax<GitHubUser[]>(url)
  .pipe(
    map((response) => response.response),
    catchError(atrapaError),
  )
  .subscribe((users) => console.log('usuarios', users));

En este ejemplo, el consumidor siempre recibe un arreglo. Si la petición falla, la UI puede renderizar un estado vacío sin romper la suscripción. Esta decisión es adecuada para listas tolerantes a fallo, pero no para operaciones donde ocultar el error afectaría consistencia o seguridad.

Imagen guia del contenido de la página

La salida de consola confirma que el error se captura dentro del pipeline y el stream continúa con el valor de reemplazo definido por of.

Warning

No uses catchError para silenciar errores críticos de forma indiscriminada. Retorna valores de fallback solo cuando el dominio lo permite; en otros casos, transforma el error y propágalo con throwError.

ajax.getJSON para consumir respuestas JSON

Cuando solo necesitas el cuerpo JSON, ajax.getJSON reduce el ruido del AjaxResponse. Este método ejecuta una petición GET, parsea la respuesta como JSON y permite enviar encabezados.

import { ajax } from 'rxjs/ajax';

interface DelayResponse {
  headers: Record<string, string>;
  origin: string;
  url: string;
}

const url = 'https://httpbin.org/delay/1';

const response$ = ajax.getJSON<DelayResponse>(url, {
  'Content-Type': 'application/json',
  'mi-token': '1234',
});

response$.subscribe({
  next: (data) => console.log('data', data),
  error: (error) => console.warn('Error en getJSON', error),
});

getJSON es una buena opción para consultas simples donde no necesitas inspeccionar status, xhr o encabezados de respuesta. Si esos metadatos importan, usa ajax directamente para conservar el contexto completo.

Métodos post, put y delete con rxjs/ajax

rxjs/ajax también expone métodos especializados para verbos HTTP comunes: ajax.get, ajax.post, ajax.put, ajax.patch y ajax.delete. Todos retornan observables y pueden participar en el mismo pipeline de operadores.

import { ajax } from 'rxjs/ajax';

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

interface HttpBinResponse<T> {
  json: T;
  url: string;
}

const url = 'https://httpbin.org/anything/users';
const headers = {
  'Content-Type': 'application/json',
  'x-client': 'rxjs-demo',
};

ajax.post<HttpBinResponse<UserPayload>>(url, { id: 1, name: 'Ada' }, headers).subscribe({
  next: (response) => console.log('POST', response.response.json),
  error: (error) => console.warn('Error en POST', error),
});

ajax.put<HttpBinResponse<UserPayload>>(`${url}/1`, { id: 1, name: 'Ada Lovelace' }, headers).subscribe({
  next: (response) => console.log('PUT', response.response.json),
  error: (error) => console.warn('Error en PUT', error),
});

ajax.delete(`${url}/1`, headers).subscribe({
  next: (response) => console.log('DELETE', response.status),
  error: (error) => console.warn('Error en DELETE', error),
});

La forma especializada es legible cuando el método HTTP es fijo y la configuración es pequeña. Para casos con más parámetros, la forma de objeto suele ser más mantenible.

Configuración avanzada con objeto

La función ajax acepta un objeto de configuración. Esta variante agrupa url, method, headers, body, responseType y otros parámetros en una sola estructura, evitando firmas largas y facilitando la composición dinámica.

import { ajax } from 'rxjs/ajax';
import { catchError, map, of } from 'rxjs';

interface CreateUserRequest {
  name: string;
  role: 'admin' | 'editor' | 'viewer';
}

interface CreateUserResponse {
  json: CreateUserRequest;
  url: string;
}

const requestBody: CreateUserRequest = {
  name: 'Grace Hopper',
  role: 'admin',
};

const createUser$ = ajax<CreateUserResponse>({
  url: 'https://httpbin.org/anything/users',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: 'Bearer demo-token',
  },
  body: requestBody,
  responseType: 'json',
}).pipe(
  map((response) => response.response.json),
  catchError((error) => {
    console.warn('No fue posible crear el usuario', error);
    return of(null);
  }),
);

createUser$.subscribe((user) => console.log('usuario creado', user));

Esta forma es especialmente útil en servicios donde construyes peticiones desde parámetros de dominio. También facilita extraer una función reutilizable que retorne un observable tipado sin ejecutar la petición hasta que exista una suscripción.

Tip

Mantén las funciones HTTP como fábricas de observables: reciben parámetros de dominio y retornan Observable<T>. Evita suscribirte dentro del servicio si el consumidor necesita cancelar, componer o manejar errores en una capa superior.

Composición con cancelación

Una de las ventajas prácticas de rxjs/ajax aparece en búsquedas o autocompletados. switchMap cancela la petición anterior cuando llega un nuevo término, evitando condiciones de carrera y respuestas obsoletas.

import { fromEvent } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { catchError, debounceTime, distinctUntilChanged, map, of, switchMap } from 'rxjs';

const input = document.querySelector<HTMLInputElement>('#search')!;

const results$ = fromEvent<InputEvent>(input, 'input').pipe(
  map((event) => (event.target as HTMLInputElement).value.trim()),
  debounceTime(300),
  distinctUntilChanged(),
  switchMap((query) => {
    if (!query) {
      return of([]);
    }

    return ajax.getJSON<unknown[]>(`/api/search?q=${encodeURIComponent(query)}`).pipe(
      catchError(() => of([])),
    );
  }),
);

results$.subscribe((results) => renderResults(results));

Con fetch, este patrón requiere coordinar manualmente AbortController para cada búsqueda pendiente. Con RxJS, la cancelación es una consecuencia directa de la desuscripción que switchMap realiza sobre el observable interno anterior.

Conclusión

rxjs/ajax convierte las peticiones HTTP en streams composables. Esta integración permite mapear respuestas, centralizar errores con catchError, cancelar trabajo pendiente mediante desuscripción y coordinar tráfico HTTP con eventos de interfaz o temporizadores.

Con este artículo cerramos la serie RxJs: partimos de la naturaleza de los observables, recorrimos funciones de creación, operadores básicos, transformación, combinación y finalmente peticiones AJAX. El objetivo común fue diseñar flujos asíncronos explícitos, cancelables y mantenibles usando las primitivas reactivas correctas.

Etiquetas :