Fetch profesional: contratos HTTP claros y UX resistente

Domina `fetch` en escenarios reales: validación de `response.ok`, parseo seguro, envío de JSON, cancelación de peticiones y manejo de estados UI sin inconsistencias.

La mayoría de bugs con red no aparecen cuando todo va bien, sino cuando el backend responde lento, devuelve error o llega una respuesta fuera de orden.

Trabajar con `fetch` a nivel profesional implica definir contrato HTTP, validar cada respuesta y sincronizar correctamente el estado visual de la interfaz.

En esta lección vas a convertir llamadas sueltas en flujos robustos: inicio, éxito, error, cancelación y limpieza final.

Este bloque se apoya en [javascript-async-pro](/curso/javascript/leccion/javascript-async-pro) y te abre camino para [javascript-formularios-pro](/curso/javascript/leccion/javascript-formularios-pro).

  • `fetch` resuelve muchas respuestas con error HTTP; la validación es tu responsabilidad.
  • `fetch()` solo rechaza por errores de red o cancelación. Un `404` o `500` normalmente llega como `Response` válida a nivel de promesa.
  • Por eso conviene un patrón fijo: pedir, validar `ok/status`, parsear y recién entonces actualizar UI. Saltarte un paso suele romper experiencia y depuración.
  • No basta con pedir datos: hay que detectar respuestas inválidas y explicarlas.
  • Al consumir endpoints para dashboard o perfiles, un `if (!res.ok)` con error contextual evita que la UI muestre datos vacíos sin explicación.

Contrato HTTP: qué validar siempre antes de renderizar

`fetch` resuelve muchas respuestas con error HTTP; la validación es tu responsabilidad.

`fetch()` solo rechaza por errores de red o cancelación. Un `404` o `500` normalmente llega como `Response` válida a nivel de promesa.

Por eso conviene un patrón fijo: pedir, validar `ok/status`, parsear y recién entonces actualizar UI. Saltarte un paso suele romper experiencia y depuración.

GET robusto: validación + parseo + mensaje útil

No basta con pedir datos: hay que detectar respuestas inválidas y explicarlas.

Al consumir endpoints para dashboard o perfiles, un `if (!res.ok)` con error contextual evita que la UI muestre datos vacíos sin explicación.

Encapsular la lectura en una función reusable te permite centralizar política de error y reducir repetición en toda la app.

  • Usa mensaje de error con endpoint y status.
  • Mantén `getJson` pequeña y predecible.
  • En UI, captura error y muestra texto de recuperación.

POST con JSON y cabeceras explícitas

Para escribir datos, método y payload deben ser inequívocos.

Enviar objetos JavaScript sin `JSON.stringify` o sin `Content-Type` correcto suele generar respuestas inesperadas o validaciones fallidas en backend.

Un patrón claro de creación: construir payload, enviar, validar respuesta y devolver objeto final para que la UI pueda confirmarlo.

Cancelación y race conditions en búsqueda en vivo

Si llegan respuestas fuera de orden, la UI puede mostrar datos viejos.

En inputs de búsqueda, cada tecla puede disparar una petición. Si no cancelas la anterior, una respuesta lenta puede sobrescribir resultados más recientes.

`AbortController` te permite cortar la petición anterior antes de lanzar la nueva y evitar inconsistencias de estado.

Estado UI acoplado al flujo de red

Un buen fetch también se nota en cómo se siente la interfaz.

Antes de pedir datos, muestra estado de carga y bloquea acciones duplicadas; al finalizar, libera controles en `finally` para que siempre vuelvan a estado usable.

Cuando hay error, evita mensajes genéricos. Explica el fallo y ofrece siguiente paso: reintentar, revisar conexión o cambiar filtro.

Código del tema: fetch() · response.ok · RequestInit · AbortController

📘 Teoría

Contrato HTTP: qué validar siempre antes de renderizar

`fetch` resuelve muchas respuestas con error HTTP; la validación es tu responsabilidad.

`fetch()` solo rechaza por errores de red o cancelación. Un `404` o `500` normalmente llega como `Response` válida a nivel de promesa.

Por eso conviene un patrón fijo: pedir, validar `ok/status`, parsear y recién entonces actualizar UI. Saltarte un paso suele romper experiencia y depuración.

1

Pedir

Lanzar `fetch(url, options)`.

  • Método adecuado
  • Headers correctos
2

Validar

Comprobar `res.ok` o `res.status`.

  • No asumir éxito
  • Error con contexto
3

Parsear

`res.json()` también puede fallar.

  • Formato esperado
  • Try/catch por flujo
4

Renderizar

Actualizar UI con estado claro.

  • loading/success/error
  • Limpieza en finally

GET robusto: validación + parseo + mensaje útil

No basta con pedir datos: hay que detectar respuestas inválidas y explicarlas.

Al consumir endpoints para dashboard o perfiles, un `if (!res.ok)` con error contextual evita que la UI muestre datos vacíos sin explicación.

Encapsular la lectura en una función reusable te permite centralizar política de error y reducir repetición en toda la app.

  • Usa mensaje de error con endpoint y status.
  • Mantén `getJson` pequeña y predecible.
  • En UI, captura error y muestra texto de recuperación.
Helper de lectura segura
async function getJson(url) {
  const res = await fetch(url);

  if (!res.ok) {
    throw new Error(`GET ${url} -> HTTP ${res.status}`);
  }

  return res.json();
}

getJson('https://jsonplaceholder.typicode.com/users/1')
  .then((user) => console.log(user.name))
  .catch((error) => console.error(error.message));

POST con JSON y cabeceras explícitas

Para escribir datos, método y payload deben ser inequívocos.

1

Enviar objetos JavaScript sin `JSON.stringify` o sin `Content-Type` correcto suele generar respuestas inesperadas o validaciones fallidas en backend.

2

Un patrón claro de creación: construir payload, enviar, validar respuesta y devolver objeto final para que la UI pueda confirmarlo.

Crear recurso con POST
async function crearTarea(payload) {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });

  if (!res.ok) throw new Error(`POST /todos -> HTTP ${res.status}`);
  return res.json();
}

crearTarea({ title: 'Aprender Fetch', completed: false, userId: 1 })
  .then((data) => console.log('Creada:', data.id));

Cancelación y race conditions en búsqueda en vivo

Si llegan respuestas fuera de orden, la UI puede mostrar datos viejos.

1

En inputs de búsqueda, cada tecla puede disparar una petición. Si no cancelas la anterior, una respuesta lenta puede sobrescribir resultados más recientes.

2

`AbortController` te permite cortar la petición anterior antes de lanzar la nueva y evitar inconsistencias de estado.

Fetch cancelable
let currentController = null;

async function buscar(query) {
  if (currentController) currentController.abort();
  currentController = new AbortController();

  try {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${query}`, {
      signal: currentController.signal
    });

    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (error) {
    if (error.name === 'AbortError') return null;
    throw error;
  }
}

Estado UI acoplado al flujo de red

Un buen fetch también se nota en cómo se siente la interfaz.

1

Antes de pedir datos, muestra estado de carga y bloquea acciones duplicadas; al finalizar, libera controles en `finally` para que siempre vuelvan a estado usable.

2

Cuando hay error, evita mensajes genéricos. Explica el fallo y ofrece siguiente paso: reintentar, revisar conexión o cambiar filtro.

Patrón de UX mínimo
Revisar
runBtn.disabled = true;
statusEl.textContent = 'Cargando datos...';

try {
  const data = await getJson('https://jsonplaceholder.typicode.com/todos/1');
  statusEl.textContent = 'Carga completada ✅';
  render(data);
} catch (error) {
  statusEl.textContent = 'No se pudo cargar. Intenta de nuevo.';
  console.error(error.message);
} finally {
  runBtn.disabled = false;
}

🧪 Aprende probando

Ejemplo Ejemplo guiado: helper reusable para GET Centraliza lectura de JSON y manejo básico de errores HTTP para evitar repetir lógica en cada endpoint.
Ejemplo Ejemplo guiado: POST para crear feedback Envía datos de formulario serializados en JSON y procesa la respuesta creada del servidor.
Ejemplo Demo interactiva: inspector de peticiones con cancelación Prueba usuarios por id, cambia rápido de valor y observa cómo se cancelan peticiones antiguas sin ensuciar la UI.
Ejemplo Demo interactiva: catálogo remoto con fetch y filtros generados Carga productos desde una API, mapea la respuesta a tarjetas y crea filtros a partir de categorías reales del JSON recibido.
Ejemplo Demo interactiva: AJAX con fetch, loading y error HTTP Carga posts desde una API pública, valida `response.ok`, renderiza tarjetas y compara el caso correcto con un 404 controlado.

🏁 Retos

Reto Reto 1: GET validado con función `main` Implementa un flujo GET que valide `res.ok`, convierta a JSON y muestre `username` en consola.
Reto Reto 2: POST con payload JSON y control de error Envía un objeto al endpoint de posts, valida respuesta y muestra `id` creado con manejo de excepciones.

¿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 .