En esta página

Fetch API

12 min lectura TextoCap. 4 — Asincronía

Qué es Fetch API?

fetch() es la API nativa del navegador para hacer peticiones HTTP. Reemplaza al antiguo XMLHttpRequest con una interfaz basada en promesas, más limpia y moderna.

const respuesta = await fetch(url, opciones);

Retorna una Response que contiene el estado HTTP, headers y métodos para leer el cuerpo.

Métodos HTTP

Método Uso Tiene body?
GET Obtener datos No
POST Crear un recurso Si
PUT Reemplazar un recurso Si
PATCH Actualizar parcialmente Si
DELETE Eliminar un recurso Generalmente no

El objeto Response

La respuesta de fetch tiene propiedades y métodos importantes:

Propiedad/Método Descripcion
response.ok true si el status es 200-299
response.status Código HTTP (200, 404, 500, etc.)
response.statusText Texto del estado ("OK", "Not Found")
response.headers Headers de la respuesta
response.json() Lee el cuerpo como JSON (promesa)
response.text() Lee el cuerpo como texto (promesa)
response.blob() Lee el cuerpo como Blob (promesa)

Nota importante sobre .ok

fetch no rechaza la promesa ante errores HTTP (404, 500, etc.). Solo rechaza ante errores de red (sin conexión, DNS fallido). Siempre verifica response.ok:

const res = await fetch('/api/data');
if (!res.ok) {
  throw new Error(`Error HTTP: ${res.status}`);
}
const data = await res.json();

Headers y body

Para enviar datos, configura los headers y el body:

const res = await fetch('/api/items', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123',
  },
  body: JSON.stringify({ nombre: 'Item', precio: 42 }),
});

El body se serializa con JSON.stringify(). Los headers indican al servidor el formato de los datos.

AbortController

Permite cancelar peticiones en curso. Es esencial para:

  • Buscadores — Cancelar la petición anterior al escribir
  • Navegación — Cancelar peticiones pendientes al cambiar de página
  • Timeouts — Cancelar peticiones que tardan demasiado
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort(); // cancela la petición

URLSearchParams

Para construir query strings de forma segura (con encoding automático):

const params = new URLSearchParams({
  buscar: 'hola mundo',
  página: '1',
});
console.log(params.toString()); // "buscar=hola+mundo&página=1"

Wrapper reutilizable

En proyectos reales, crea una función wrapper que centralice la lógica comun: base URL, headers por defecto, manejo de errores y serialización.


Práctica

  1. Haz un GET con validacion: Escribe una funcion async obtenerDatos(url) que haga un fetch GET, verifique response.ok, y retorne los datos parseados como JSON. Maneja los errores HTTP lanzando un Error con el codigo de estado.
  2. Envia datos con POST: Crea una funcion enviarFormulario(url, datos) que haga un fetch POST con Content-Type: application/json y el body serializado con JSON.stringify. Verifica la respuesta y retorna el resultado.
  3. Construye una URL con parametros: Usa URLSearchParams para construir una URL con los parametros buscar, pagina y limite. Imprime la URL completa en consola.

En la siguiente leccion aprenderemos sobre módulos ES para organizar nuestro código.

fetch no lanza error en HTTP 4xx/5xx
A diferencia de otras librerias (como Axios), fetch solo rechaza la promesa si hay un error de red. Respuestas 404 o 500 se consideran exitosas. Siempre verifica response.ok antes de procesar los datos.
AbortController para busquedas
En campos de busqueda, cancela la petición anterior antes de hacer una nueva. Esto evita condiciones de carrera donde una respuesta vieja llega despues de una nueva.
javascript
// GET básico
async function obtenerUsuarios() {
  const respuesta = await fetch('https://api.ejemplo.com/users');

  // Verificar estado HTTP
  if (!respuesta.ok) {
    throw new Error(`HTTP ${respuesta.status}: ${respuesta.statusText}`);
  }

  const usuarios = await respuesta.json();
  return usuarios;
}

// POST - enviar datos
async function crearUsuario(datos) {
  const respuesta = await fetch('https://api.ejemplo.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(datos),
  });

  if (!respuesta.ok) {
    const error = await respuesta.json();
    throw new Error(error.message);
  }

  return respuesta.json();
}

// PUT - actualizar
async function actualizarUsuario(id, datos) {
  return fetch(`https://api.ejemplo.com/users/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(datos),
  });
}

// DELETE
async function eliminarUsuario(id) {
  const res = await fetch(`https://api.ejemplo.com/users/${id}`, {
    method: 'DELETE',
  });
  if (!res.ok) throw new Error('No se pudo eliminar');
}

// Uso
const nuevo = await crearUsuario({
  nombre: 'Maria',
  email: '[email protected]',
});
console.log('Creado:', nuevo);
javascript
// AbortController - cancelar peticiones
const controller = new AbortController();

async function buscar(término) {
  try {
    const res = await fetch(`/api/search?q=${término}`, {
      signal: controller.signal,
    });
    return res.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Peticion cancelada');
    } else {
      throw error;
    }
  }
}

// Cancelar la petición
controller.abort();

// Query parameters con URLSearchParams
function construirURL(base, params) {
  const url = new URL(base);
  const searchParams = new URLSearchParams(params);
  url.search = searchParams.toString();
  return url.toString();
}

const url = construirURL('https://api.ejemplo.com/products', {
  categoria: 'tech',
  orden: 'precio',
  página: '1',
  limite: '20',
});
// https://api.ejemplo.com/products?categoria=tech&orden=precio&página=1&limite=20

// Wrapper reutilizable
async function api(endpoint, opciones = {}) {
  const config = {
    headers: { 'Content-Type': 'application/json' },
    ...opciones,
    body: opciones.body ? JSON.stringify(opciones.body) : undefined,
  };

  const respuesta = await fetch(`/api${endpoint}`, config);

  if (!respuesta.ok) {
    const error = await respuesta.json().catch(() => ({}));
    throw new Error(error.message || `HTTP ${respuesta.status}`);
  }

  return respuesta.json();
}

// Uso del wrapper
const users = await api('/users');
const post = await api('/posts', {
  method: 'POST',
  body: { título: 'Nuevo post', contenido: '...' },
});