La Fetch API es la forma moderna de hacer peticiones HTTP en JavaScript, reemplazando a XMLHttpRequest con una interfaz basada en Promises mucho más limpia. Está disponible de forma nativa en todos los navegadores modernos y en Node.js desde la versión 18, y funciona perfectamente con async/await.
GET básico: leer datos de una API
fetch(url) devuelve una Promise que resuelve a un objeto Response. Hay que llamar a response.json() (u otros métodos) para leer el cuerpo, ya que también devuelve una Promise:
async function obtenerUsuario(id) {
const response = await fetch(`https://api.ejemplo.com/usuarios/${id}`);
// El error más frecuente: fetch NO lanza error con status 4xx/5xx
// response.ok es false si status >= 400
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const usuario = await response.json();
return usuario;
}
try {
const usuario = await obtenerUsuario(1);
console.log(usuario.nombre);
} catch (err) {
// Aquí llegan tanto errores de red como los throw manuales
console.error('Error:', err.message);
}
// Información de la respuesta:
// response.status ? 200, 404, 500...
// response.ok ? true si status 200-299
// response.headers ? objeto Headers
// response.url ? URL final (tras redirecciones)
POST: enviar datos
Para POST, hay que especificar el método, el tipo de contenido y el cuerpo serializado:
async function crearUsuario(datos) {
const response = await fetch('/api/usuarios', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(datos)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.mensaje || `HTTP ${response.status}`);
}
return response.json();
}
const nuevoUsuario = await crearUsuario({
nombre: 'Ana',
email: '[email protected]',
rol: 'editor'
});
console.log('Creado con ID:', nuevoUsuario.id);
// Enviar FormData (para uploads de archivos):
async function subirArchivo(archivo) {
const formData = new FormData();
formData.append('archivo', archivo);
formData.append('nombre', archivo.name);
// Con FormData, NO pongas Content-Type: el navegador lo establece
// automáticamente con el boundary correcto
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return response.json();
}
Leer diferentes tipos de respuesta
Response ofrece varios métodos para leer el cuerpo según el formato:
const r = await fetch('/api/datos');
// Solo se puede leer el body UNA VEZ
await r.json(); // Parsea como JSON
await r.text(); // Texto plano
await r.blob(); // Archivo binario (imagen, PDF...)
await r.arrayBuffer(); // Buffer de bytes
await r.formData(); // FormData
// Para imagen:
async function obtenerImagen(url) {
const r = await fetch(url);
const blob = await r.blob();
const urlObjeto = URL.createObjectURL(blob);
document.querySelector('img').src = urlObjeto;
}
// Verificar el Content-Type antes de parsear:
async function obtenerDatos(url) {
const r = await fetch(url);
const tipo = r.headers.get('content-type') ?? '';
if (tipo.includes('application/json')) {
return r.json();
} else if (tipo.includes('text/')) {
return r.text();
} else {
return r.blob();
}
}
Cancelar peticiones con AbortController
Si el usuario navega a otra pantalla antes de que llegue la respuesta, o si quieres implementar un timeout, necesitas cancelar la petición:
// Cancelación manual
const controller = new AbortController();
const { signal } = controller;
const promesa = fetch('/api/datos', { signal });
// Cancelar tras 3 segundos:
const timeout = setTimeout(() => controller.abort(), 3000);
try {
const datos = await promesa;
clearTimeout(timeout);
return datos.json();
} catch (err) {
if (err.name === 'AbortError') {
console.log('Petición cancelada');
} else {
throw err;
}
}
// En React: cancelar al desmontar el componente
useEffect(() => {
const controller = new AbortController();
fetch('/api/datos', { signal: controller.signal })
.then(r => r.json())
.then(setDatos)
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort(); // Limpieza al desmontar
}, []);
fetch vs axios: cuándo elegir cada uno
// Ventajas de fetch (nativo):
// - Sin dependencias
// - Nativo en navegadores y Node 18+
// - API más cercana al estándar HTTP
// Ventajas de axios sobre fetch:
// - Lanza error automáticamente con status >= 400 (fetch no lo hace)
// - Transforma automáticamente JSON en request y response
// - Interceptores de request/response
// - Cancelación con CancelToken (aunque fetch+AbortController es similar)
// - Soporte de progreso de upload
// Wrapper básico de fetch que se comporta como axios:
async function api(url, opciones = {}) {
const { data, ...resto } = opciones;
const config = {
...resto,
headers: {
'Content-Type': 'application/json',
...resto.headers
},
body: data ? JSON.stringify(data) : undefined
};
const r = await fetch(url, config);
if (!r.ok) {
const err = new Error(`HTTP ${r.status}`);
err.response = r;
throw err;
}
return r.json();
}
El punto más importante para recordar: fetch solo lanza errores en caso de fallo de red o de cancelación. Un status 404 o 500 desde el servidor no es un error para fetch: la Promise se resuelve normalmente. Siempre comprueba response.ok o el status code.
