Fetch API
Introducción a la API Fetch
La comunicación con servidores es una parte fundamental de las aplicaciones web modernas. Constantemente necesitamos enviar y recibir datos: obtener información de usuario, publicar contenido, cargar recursos o consumir servicios externos. Durante años, la forma estándar de realizar estas operaciones fue mediante el objeto XMLHttpRequest
, pero en la actualidad disponemos de una alternativa más moderna, potente y fácil de usar: la API Fetch.
Fetch es una interfaz nativa de JavaScript para realizar peticiones HTTP de manera asíncrona. Diseñada como un reemplazo más potente y flexible para XMLHttpRequest, esta API facilita enormemente el consumo de recursos y la comunicación con servicios web, introduciendo un enfoque basado en promesas que se integra perfectamente con las características modernas de JavaScript.
En este artículo, exploraremos cómo utilizar Fetch para realizar diferentes tipos de peticiones HTTP, gestionar respuestas y aprovechar al máximo esta poderosa herramienta en nuestras aplicaciones web.
Sintaxis básica y opciones
La forma más simple de utilizar Fetch es pasando una URL como parámetro:
fetch('https://api.ejemplo.com/datos')
.then(respuesta => {
// Manejar la respuesta
console.log('Respuesta recibida:', respuesta);
})
.catch(error => {
// Manejar errores
console.error('Error en la petición:', error);
});
Este código realiza una petición GET básica a la URL especificada. Fetch devuelve una promesa que se resuelve con un objeto Response
, que contiene la respuesta del servidor.
Sin embargo, Fetch acepta un segundo parámetro opcional que nos permite configurar diversos aspectos de la petición:
fetch('https://api.ejemplo.com/datos', {
method: 'GET', // Método HTTP: GET, POST, PUT, DELETE, etc.
headers: { // Cabeceras HTTP
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
mode: 'cors', // Modo CORS: cors, no-cors, same-origin
cache: 'no-cache', // Estrategia de caché
credentials: 'same-origin', // Incluir/excluir cookies: omit, same-origin, include
redirect: 'follow', // Manejo de redirecciones: follow, error, manual
referrerPolicy: 'no-referrer', // Política de referrer
body: JSON.stringify({ // Cuerpo de la petición (no válido para GET/HEAD)
clave: 'valor',
otroParametro: 42
})
})
.then(respuesta => respuesta.json())
.then(datos => {
console.log('Datos recibidos:', datos);
})
.catch(error => {
console.error('Error:', error);
});
Veamos algunas de las opciones más importantes:
- method: Especifica el método HTTP a utilizar. Por defecto es 'GET'.
- headers: Objeto con las cabeceras HTTP para enviar con la petición.
- body: El cuerpo de la petición. Puede ser una cadena, un objeto FormData, un Blob, etc.
- credentials: Controla si se envían cookies con la petición.
- mode: Controla cómo se manejan las peticiones entre diferentes orígenes (CORS).
Envío de solicitudes GET
Las solicitudes GET son las más comunes y se utilizan para obtener datos de un servidor. Por defecto, fetch realiza una petición GET si no especificamos el método:
// Función para obtener una lista de usuarios
async function obtenerUsuarios() {
try {
// Realizar la petición GET
const respuesta = await fetch('https://api.ejemplo.com/usuarios');
// Verificar si la respuesta fue exitosa
if (!respuesta.ok) {
throw new Error(`Error HTTP: ${respuesta.status}`);
}
// Convertir la respuesta a JSON
const usuarios = await respuesta.json();
// Procesar los datos
console.log('Usuarios obtenidos:', usuarios);
return usuarios;
} catch (error) {
console.error('Error al obtener usuarios:', error);
throw error; // Propagar el error para manejo posterior
}
}
// Uso de la función con async/await
async function mostrarUsuarios() {
try {
const usuarios = await obtenerUsuarios();
// Ahora podemos trabajar con los usuarios
usuarios.forEach(usuario => {
console.log(`Nombre: ${usuario.nombre}, Email: ${usuario.email}`);
});
} catch (error) {
// Manejar el error
console.error('No se pudieron mostrar los usuarios:', error);
}
}
Petición GET con parámetros de consulta
// Función para buscar usuarios según criterios
function buscarUsuarios(criterios) {
// Construir la URL con parámetros de consulta
const url = new URL('https://api.ejemplo.com/usuarios/buscar');
// Añadir parámetros de búsqueda
Object.keys(criterios).forEach(key => {
url.searchParams.append(key, criterios[key]);
});
// Realizar la petición
return fetch(url)
.then(respuesta => {
if (!respuesta.ok) throw new Error('Error en la búsqueda');
return respuesta.json();
});
}
// Ejemplo de uso
buscarUsuarios({
nombre: 'Ana',
ciudad: 'Madrid',
edad_minima: 25
})
.then(resultados => {
console.log('Resultados de búsqueda:', resultados);
})
.catch(error => {
console.error('Error:', error);
});
En este ejemplo, se generará una URL como https://api.ejemplo.com/usuarios/buscar?nombre=Ana&ciudad=Madrid&edad_minima=25
.
Envío de solicitudes POST y otros métodos
Para enviar datos al servidor, normalmente utilizamos el método POST. Otros métodos comunes incluyen PUT (para actualizar recursos), DELETE (para eliminar recursos) y PATCH (para actualizar parcialmente recursos).
Ejemplo de POST
// Función para crear un nuevo usuario
function crearUsuario(datosUsuario) {
return fetch('https://api.ejemplo.com/usuarios', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(datosUsuario)
})
.then(respuesta => {
if (!respuesta.ok) {
// Si el servidor responde con un error, lo procesamos
return respuesta.json().then(errorDatos => {
throw new Error(errorDatos.mensaje || 'Error al crear usuario');
});
}
return respuesta.json();
});
}
// Ejemplo de uso
const nuevoUsuario = {
nombre: 'Carlos Rodríguez',
email: 'carlos@ejemplo.com',
edad: 32,
ciudad: 'Barcelona'
};
crearUsuario(nuevoUsuario)
.then(usuarioCreado => {
console.log('Usuario creado con éxito:', usuarioCreado);
})
.catch(error => {
console.error('Error al crear usuario:', error);
});
Ejemplo de PUT
// Función para actualizar un usuario existente
function actualizarUsuario(id, datosActualizados) {
return fetch(`https://api.ejemplo.com/usuarios/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(datosActualizados)
})
.then(respuesta => {
if (!respuesta.ok) throw new Error('Error al actualizar');
return respuesta.json();
});
}
// Ejemplo de uso
actualizarUsuario(123, {
edad: 33,
ciudad: 'Valencia'
})
.then(resultado => {
console.log('Usuario actualizado:', resultado);
})
.catch(error => {
console.error('Error:', error);
});
Ejemplo de DELETE
// Función para eliminar un usuario
function eliminarUsuario(id) {
return fetch(`https://api.ejemplo.com/usuarios/${id}`, {
method: 'DELETE'
})
.then(respuesta => {
if (!respuesta.ok) throw new Error('Error al eliminar');
// Algunos endpoints devuelven un cuerpo vacío en DELETE
// En ese caso, simplemente retornamos true
if (respuesta.status === 204) {
return true;
}
return respuesta.json();
});
}
// Ejemplo de uso
eliminarUsuario(123)
.then(resultado => {
console.log('Usuario eliminado correctamente');
})
.catch(error => {
console.error('Error al eliminar:', error);
});
Trabajo con headers y body
Las cabeceras HTTP (headers) y el cuerpo (body) de las peticiones y respuestas son componentes importantes en la comunicación con APIs.
Manipulación de headers
Las cabeceras permiten enviar metadatos adicionales con nuestras peticiones, como tokens de autorización, preferencias de formato o información del cliente:
// Configuración común de headers
const headers = new Headers({
'Content-Type': 'application/json',
'Accept-Language': 'es-ES',
'X-API-Key': 'abc123xyz789'
});
// Añadir una cabecera individualmente
headers.append('User-Agent', 'MiAplicacionWeb/1.0');
// Verificar si existe una cabecera
if (headers.has('X-API-Key')) {
console.log('La clave API está presente');
}
// Obtener el valor de una cabecera
const apiKey = headers.get('X-API-Key');
// Usar las cabeceras en una petición
fetch('https://api.ejemplo.com/datos', {
headers: headers
})
.then(respuesta => respuesta.json())
.then(datos => console.log(datos));
Trabajando con el cuerpo de la respuesta
El objeto Response proporciona varios métodos para procesar el cuerpo de la respuesta en diferentes formatos:
fetch('https://api.ejemplo.com/datos')
.then(respuesta => {
// Información sobre la respuesta
console.log('Status:', respuesta.status);
console.log('OK:', respuesta.ok);
console.log('Tipo de contenido:', respuesta.headers.get('Content-Type'));
// Dependiendo del tipo de contenido, procesamos la respuesta
if (respuesta.headers.get('Content-Type').includes('application/json')) {
return respuesta.json(); // Datos JSON
} else if (respuesta.headers.get('Content-Type').includes('text/')) {
return respuesta.text(); // Contenido de texto
} else if (respuesta.headers.get('Content-Type').includes('image/')) {
return respuesta.blob(); // Datos binarios como imágenes
} else {
return respuesta.arrayBuffer(); // Datos binarios genéricos
}
})
.then(datos => {
console.log('Datos procesados:', datos);
});
Los métodos más comunes para procesar el cuerpo de la respuesta son:
- json(): Convierte el cuerpo a un objeto JavaScript (para respuestas en formato JSON)
- text(): Devuelve el cuerpo como texto
- blob(): Para datos binarios como imágenes
- arrayBuffer(): Para datos binarios genéricos
- formData(): Para datos en formato FormData
Es importante recordar que estos métodos solo pueden ser llamados una vez, ya que consumen el cuerpo de la respuesta.
Manejo de respuestas
Un aspecto fundamental al trabajar con Fetch es comprender cómo gestionar diferentes tipos de respuestas y estados HTTP:
fetch('https://api.ejemplo.com/recurso')
.then(respuesta => {
// Verificar el estado de la respuesta
if (respuesta.status === 200) {
console.log('Respuesta exitosa');
} else if (respuesta.status === 404) {
console.log('Recurso no encontrado');
throw new Error('No se encontró el recurso solicitado');
} else if (respuesta.status === 401) {
console.log('No autorizado');
throw new Error('Se requiere autenticación');
} else if (respuesta.status >= 500) {
console.log('Error del servidor');
throw new Error('Error interno del servidor');
}
// Procesar la respuesta
return respuesta.json();
})
.then(datos => {
// Trabajar con los datos
console.log('Datos:', datos);
})
.catch(error => {
// Manejar cualquier error ocurrido
console.error('Error en la operación:', error);
});
Un patrón común es verificar la propiedad ok
del objeto Response, que es true
para códigos de estado en el rango 200-299:
fetch('https://api.ejemplo.com/datos')
.then(respuesta => {
if (!respuesta.ok) {
throw new Error(`Error HTTP: ${respuesta.status}`);
}
return respuesta.json();
})
.then(datos => {
console.log('Datos recibidos:', datos);
})
.catch(error => {
console.error('Error:', error);
});
Ventajas sobre XMLHttpRequest
La API Fetch ofrece varias ventajas significativas frente a XMLHttpRequest (XHR):
-
Sintaxis más simple y limpia:
- XHR requiere varios pasos y configuraciones para una petición simple
- Fetch utiliza un enfoque más directo y legible
-
Basada en Promesas:
- XHR utiliza callbacks, que pueden llevar a "callback hell"
- Fetch devuelve promesas, que facilitan el encadenamiento y el uso de async/await
-
Mejor manejo de errores:
- La gestión de errores es más intuitiva con Fetch gracias a .catch()
- Se integra perfectamente con los bloques try/catch de async/await
-
Request y Response más potentes:
- Fetch proporciona objetos especializados para peticiones y respuestas
- Ofrece métodos específicos para diferentes tipos de contenido (json, text, blob, etc.)
-
Mejor soporte para streaming:
- Fetch permite el procesamiento de datos en streaming, lo que es más eficiente para grandes volúmenes de datos
Integración con async/await
Una de las grandes ventajas de Fetch es su perfecta integración con async/await, lo que nos permite escribir código asíncrono que parece síncrono, mejorando considerablemente la legibilidad:
// Función asíncrona para obtener datos de múltiples recursos
async function obtenerDatosCompletos(idUsuario) {
try {
// Obtener información básica del usuario
const respuestaUsuario = await fetch(`https://api.ejemplo.com/usuarios/${idUsuario}`);
if (!respuestaUsuario.ok) throw new Error('Error al obtener usuario');
const usuario = await respuestaUsuario.json();
// Obtener publicaciones del usuario
const respuestaPublicaciones = await fetch(`https://api.ejemplo.com/usuarios/${idUsuario}/publicaciones`);
if (!respuestaPublicaciones.ok) throw new Error('Error al obtener publicaciones');
const publicaciones = await respuestaPublicaciones.json();
// Obtener comentarios del usuario
const respuestaComentarios = await fetch(`https://api.ejemplo.com/usuarios/${idUsuario}/comentarios`);
if (!respuestaComentarios.ok) throw new Error('Error al obtener comentarios');
const comentarios = await respuestaComentarios.json();
// Combinar todos los datos
return {
perfil: usuario,
publicaciones: publicaciones,
comentarios: comentarios
};
} catch (error) {
console.error('Error obteniendo datos completos:', error);
throw error;
}
}
// Uso de la función
async function mostrarPerfilCompleto(idUsuario) {
try {
const datosCompletos = await obtenerDatosCompletos(idUsuario);
console.log('Nombre:', datosCompletos.perfil.nombre);
console.log('Total de publicaciones:', datosCompletos.publicaciones.length);
console.log('Total de comentarios:', datosCompletos.comentarios.length);
// Aquí podríamos actualizar la interfaz con estos datos
} catch (error) {
console.error('No se pudo mostrar el perfil:', error);
// Mostrar mensaje de error al usuario
}
}
// Llamar a la función con un ID de usuario
mostrarPerfilCompleto(123);
Este enfoque con async/await hace que el código sea mucho más fácil de leer, entender y mantener, especialmente cuando necesitamos realizar múltiples peticiones secuenciales o dependientes entre sí.
Resumen
La API Fetch representa una evolución significativa en la forma de realizar peticiones HTTP desde JavaScript. Su sintaxis clara, su enfoque basado en promesas y su perfecta integración con características modernas como async/await la convierten en la opción preferida para la comunicación cliente-servidor en aplicaciones web actuales.
A lo largo de este artículo, hemos explorado cómo utilizar Fetch para realizar diferentes tipos de peticiones, gestionar respuestas y aprovechar sus capacidades para construir aplicaciones robustas. Aunque hemos cubierto los aspectos más importantes, Fetch ofrece muchas más posibilidades que se pueden explorar a medida que profundices en tu desarrollo con JavaScript.
En el próximo artículo, exploraremos con más detalle los métodos HTTP y cómo se utilizan en el contexto de las APIs RESTful, un conocimiento fundamental para cualquier desarrollador web moderno.