Fetch avanzado: cancelar, reintentar y evitar carreras

Aprende a construir peticiones HTTP resistentes con AbortController, timeouts, retry con backoff y protección contra respuestas fuera de orden en interfaces dinámicas.

En producción, las peticiones no siempre responden rápido ni bien: hay timeouts, cortes intermitentes y respuestas antiguas que llegan tarde.

Un flujo robusto de fetch necesita tres pilares: cancelar solicitudes obsoletas, reintentar fallos temporales y validar estados HTTP explícitamente.

Sin estos patrones, la UI puede mostrar datos inconsistentes o generar doble carga innecesaria al backend.

Objetivo de esta lección: diseñar capa de red resiliente y predecible usando abort, retry y control de concurrencia.

  • Fetch no termina en await: debes pensar en fallos reales de infraestructura.
  • `fetch` solo rechaza en errores de red; respuestas 404/500 no lanzan por sí mismas. Por eso debes comprobar `response.ok` antes de parsear.
  • Además, en interfaces con búsquedas o filtros rápidos, necesitas cancelar solicitudes previas para evitar que una respuesta antigua pise la nueva.
  • Si la respuesta tarda demasiado, cancelas de forma explícita.
  • Combinar `AbortController` con `setTimeout` permite imponer un límite de espera y liberar la UI cuando la red está degradada.

Mentalidad de resiliencia en red

Fetch no termina en await: debes pensar en fallos reales de infraestructura.

`fetch` solo rechaza en errores de red; respuestas 404/500 no lanzan por sí mismas. Por eso debes comprobar `response.ok` antes de parsear.

Además, en interfaces con búsquedas o filtros rápidos, necesitas cancelar solicitudes previas para evitar que una respuesta antigua pise la nueva.

AbortController y timeout manual

Si la respuesta tarda demasiado, cancelas de forma explícita.

Combinar `AbortController` con `setTimeout` permite imponer un límite de espera y liberar la UI cuando la red está degradada.

Recuerda limpiar el timer en `finally` para evitar fugas y comportamientos inesperados.

  • Abortar no significa error fatal: puede ser decisión de UX.
  • Diferencia AbortError de otros errores en tu capa de UI.
  • Evita timeouts excesivos que bloqueen feedback al usuario.
  • Centraliza helper para usar política consistente en toda la app.

Retry con backoff para fallos temporales

No reintentes todo sin criterio: define cuándo y cuántas veces.

Un retry lineal/agresivo puede amplificar carga del sistema. Mejor aplica backoff exponencial corto y límite de intentos.

Reintenta solo en errores potencialmente recuperables (timeouts, 429, 503), no en validaciones funcionales.

Evitar race conditions en búsquedas y filtros

La respuesta más lenta no debe sobrescribir la más reciente.

En inputs de búsqueda, cada tecla puede lanzar un fetch nuevo. Si no abortas el anterior, una respuesta vieja podría pintar resultados incorrectos.

La combinación de `AbortController` + token/contador de request te da control fino sobre qué respuesta es válida.

  • Aborta request anterior cuando cambie el criterio de búsqueda.
  • Ignora AbortError en UI para no mostrar falso error al usuario.
  • Muestra estado de loading ligado a la petición vigente.
  • Evita múltiples fuentes de verdad para resultados.

Checklist de fetch robusto en producción

Con este protocolo, tu capa de red será más confiable y mantenible.

El valor de estos patrones aparece cuando hay mala red, backend inestable o uso intenso de la interfaz.

Revisar cancelación, reintentos y estados HTTP antes de desplegar evita gran parte de bugs intermitentes difíciles de reproducir.

  • Validación explícita de `response.ok`.
  • AbortController para timeout y cancelación de obsoletos.
  • Retry acotado con backoff y criterio de idempotencia.
  • Mensajes de UX claros diferenciando cancelado, temporal y fallo definitivo.
JavaScript
38

Fetch avanzado: cancelar, reintentar y evitar carreras

Aprende a construir peticiones HTTP resistentes con AbortController, timeouts, retry con backoff y protección contra respuestas fuera de orden en interfaces dinámicas.

Código del tema: fetch + AbortController + retry/backoff

📘 Teoría

Mentalidad de resiliencia en red

Fetch no termina en await: debes pensar en fallos reales de infraestructura.

`fetch` solo rechaza en errores de red; respuestas 404/500 no lanzan por sí mismas. Por eso debes comprobar `response.ok` antes de parsear.

Además, en interfaces con búsquedas o filtros rápidos, necesitas cancelar solicitudes previas para evitar que una respuesta antigua pise la nueva.

1

Abortar

2

Reintentar

3

Validar

4

Aislar

AbortController y timeout manual

Si la respuesta tarda demasiado, cancelas de forma explícita.

Combinar `AbortController` con `setTimeout` permite imponer un límite de espera y liberar la UI cuando la red está degradada.

Recuerda limpiar el timer en `finally` para evitar fugas y comportamientos inesperados.

  • Abortar no significa error fatal: puede ser decisión de UX.
  • Diferencia AbortError de otros errores en tu capa de UI.
  • Evita timeouts excesivos que bloqueen feedback al usuario.
  • Centraliza helper para usar política consistente en toda la app.
Fetch con timeout
async function fetchConTimeout(url, ms = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), ms);

  try {
    const res = await fetch(url, { signal: controller.signal });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } finally {
    clearTimeout(timeoutId);
  }
}

document.getElementById('abortable').addEventListener('click', async () => {
  const stateEl = document.getElementById('abortState');
  stateEl.textContent = 'Lanzando petición... (fallará en 2s)';
  try {
    // Usamos una URL que responde lento para forzar el timeout
    await fetchConTimeout('https://httpbin.org/delay/5', 2000);
    stateEl.textContent = 'Éxito (no debería llegar aquí)';
  } catch (error) {
    stateEl.textContent = `Falló: ${error.message}`;
  }
});

Retry con backoff para fallos temporales

No reintentes todo sin criterio: define cuándo y cuántas veces.

1

Un retry lineal/agresivo puede amplificar carga del sistema. Mejor aplica backoff exponencial corto y límite de intentos.

2

Reintenta solo en errores potencialmente recuperables (timeouts, 429, 503), no en validaciones funcionales.

Helper de retry
function esperar(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function fetchConRetry(url, maxIntentos = 3) {
  let intento = 0;

  while (intento < maxIntentos) {
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.json();
    } catch (error) {
      intento += 1;
      if (intento >= maxIntentos) throw error;
      await esperar(300 * 2 ** (intento - 1));
    }
  }
}

Evitar race conditions en búsquedas y filtros

La respuesta más lenta no debe sobrescribir la más reciente.

En inputs de búsqueda, cada tecla puede lanzar un fetch nuevo. Si no abortas el anterior, una respuesta vieja podría pintar resultados incorrectos.

La combinación de `AbortController` + token/contador de request te da control fino sobre qué respuesta es válida.

  • Aborta request anterior cuando cambie el criterio de búsqueda.
  • Ignora AbortError en UI para no mostrar falso error al usuario.
  • Muestra estado de loading ligado a la petición vigente.
  • Evita múltiples fuentes de verdad para resultados.
Última petición gana
let controladorActivo = null;

async function buscar(q) {
  controladorActivo?.abort();
  controladorActivo = new AbortController();

  const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, {
    signal: controladorActivo.signal
  });

  if (!res.ok) throw new Error('Error en búsqueda');
  return res.json();
}

Checklist de fetch robusto en producción

Con este protocolo, tu capa de red será más confiable y mantenible.

El valor de estos patrones aparece cuando hay mala red, backend inestable o uso intenso de la interfaz.

Revisar cancelación, reintentos y estados HTTP antes de desplegar evita gran parte de bugs intermitentes difíciles de reproducir.

  • Validación explícita de `response.ok`.
  • AbortController para timeout y cancelación de obsoletos.
  • Retry acotado con backoff y criterio de idempotencia.
  • Mensajes de UX claros diferenciando cancelado, temporal y fallo definitivo.

🧪 Aprende probando

Ejemplo Ejemplo guiado: fetch cancelable con timeout Implementa un helper que cancela la petición si supera el tiempo máximo.
Ejemplo Ejemplo guiado: retry con backoff Reintenta hasta N veces en fallos transitorios para mejorar resiliencia.
Ejemplo Demo interactiva: búsqueda con cancelación Simula búsquedas rápidas donde se aborta la solicitud anterior para evitar resultados fuera de orden.

🏁 Retos

Reto Reto 1: construir fetchConTimeout Implementa una función que cancele la petición al superar un tiempo máximo.
Reto Reto 2: construir fetchConRetry Implementa reintentos con espera incremental antes de fallar definitivamente.

¿Qué es esto?

Soy Cristian Eslava y a veces hago webs para procrastinar yo y vosotros 😉.

Esta la hice en febrero de 2026 para facilitar el aprendizaje de mis alumnxs. Aprender desarrollo web practicando. La idea es que crezca semanalmente con nuevos temas, tests y retos.

Inspirado en MDN, en W3Schools, en Codepen, en el crack de Manz y en mil sitios de documentación sobre desarrollo web. Quería aportar además de bloques teóricos con ejemplos, la gamificación de los retos y el sistema de test que ya tenía en culTest .

Si te gustó, si no te gustó, si quieres saludarme, o invitarme a 🍻 no dudes en escribirme en cristianeslava@gmail.com .