La API Fetch ha revolucionado la forma en que interactuamos con servidores web desde JavaScript, reemplazando eficazmente al obsoleto XMLHttpRequest (XHR). Integrada nativamente en todos los navegadores modernos y en Node.js 18+, esta API se basa en promesas (Promises), lo que resulta en un código más limpio y fácil de entender. Si aún recurres a librerías como jQuery.ajax() o Axios para peticiones sencillas, es hora de reconsiderarlo y abrazar el poder nativo de Fetch.

Realizando Peticiones GET Sencillas

Petición GET
// GET simple
const response = await fetch('https://api.ejemplo.com/usuarios');
const usuarios = await response.json();
console.log(usuarios);

// Con manejo de errores
async function obtenerUsuarios() {
  const response = await fetch('https://api.ejemplo.com/usuarios');

  if (!response.ok) {
    // Lanzamos un error si la respuesta HTTP no es exitosa (e.g., 404, 500)
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  // Parseamos la respuesta JSON
  return response.json();
}

Punto Clave: Es fundamental entender que fetch() solo rechaza la promesa en caso de errores de red (cuando el servidor no es alcanzable). Las respuestas con códigos de error HTTP (como 404 "No encontrado" o 500 "Error del servidor") se consideran exitosas desde la perspectiva de la red. Por ello, debes verificar manualmente la propiedad response.ok para asegurarte de que la petición fue realmente exitosa a nivel de aplicación.

Peticiones POST, PUT y DELETE: Modificando Recursos

POST — Crear un Recurso
const response = await fetch('https://api.ejemplo.com/usuarios', {
  method: 'POST', // Especificamos el método HTTP
  headers: {
    'Content-Type': 'application/json', // Indicamos que enviamos JSON
    'Authorization': 'Bearer eyJhbGci...', // Token de autenticación (ejemplo)
  },
  body: JSON.stringify({ // Convertimos el objeto JavaScript a una cadena JSON
    nombre: 'Alicia',
    email: '[email protected]',
  }),
});

const nuevoUsuario = await response.json(); // Procesamos la respuesta del servidor
PUT — Actualizar un Recurso
await fetch('https://api.ejemplo.com/usuarios/123', { // Especificamos el ID del usuario a actualizar
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ nombre: 'Alicia Actualizada' }), // Enviamos los datos a actualizar
});
DELETE — Eliminar un Recurso
await fetch('https://api.ejemplo.com/usuarios/123', { // Especificamos el ID del usuario a eliminar
  method: 'DELETE',
  headers: { 'Authorization': 'Bearer token123' }, // Autorización necesaria
});

Interpretando Diferentes Tipos de Respuestas

Parseo de Respuestas Variadas
const response = await fetch(url);

// Para respuestas en formato JSON
const datosJson = await response.json();

// Para respuestas en texto plano
const textoPlano = await response.text();

// Para datos binarios (imágenes, archivos)
const objetoBlob = await response.blob();

// Para datos binarios crudos (ArrayBuffer)
const bufferBinario = await response.arrayBuffer();

// Para formularios enviados como 'multipart/form-data'
const datosFormulario = await response.formData();

// Metadatos de la respuesta
console.log(response.status);      // Código de estado HTTP (ej: 200)
console.log(response.statusText);  // Texto descriptivo del estado (ej: "OK")
console.log(response.ok);          // Boolean: true si el código de estado está entre 200 y 299
console.log(response.headers.get('Content-Type')); // Obtener un encabezado específico

AbortController: Cancelando Peticiones en Curso

Abortar una Petición Fetch
const controller = new AbortController();

// Programamos la cancelación de la petición después de 5 segundos
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('https://api.ejemplo.com/datos-pesados', {
    signal: controller.signal, // Asociamos la señal del controlador
  });
  clearTimeout(timeoutId); // Si la respuesta llega antes, cancelamos el timeout
  const data = await response.json();
  // Procesar los datos...
} catch (err) {
  if (err.name === 'AbortError') {
    // Manejamos específicamente el error de cancelación
    console.log('La petición fue cancelada por tiempo de espera');
  } else {
    // Lanzamos otros errores de red o de procesamiento
    throw err;
  }
}

Carga de Archivos con Fetch

Subir Archivos usando FormData
const formularioDatos = new FormData();
// Añadimos el archivo seleccionado por el usuario
formularioDatos.append('avatar', inputArchivo.files[0]);
// Añadimos otros campos si son necesarios
formularioDatos.append('nombreUsuario', 'Alicia');

// Importante: No establecemos 'Content-Type'. El navegador lo hace automáticamente
// para 'multipart/form-data', incluyendo el 'boundary' necesario.
const response = await fetch('/api/upload-avatar', {
  method: 'POST',
  body: formularioDatos, // Pasamos el objeto FormData como cuerpo de la petición
});

Creando un Wrapper Reutilizable para Fetch

Cliente API Personalizado
class ClienteApi {
  constructor(baseUrl, token) {
    this.baseUrl = baseUrl; // URL base de tu API
    this.token = token;     // Token de autenticación
  }

  async peticion(ruta, opciones = {}) {
    const urlCompleta = `${this.baseUrl}${ruta}`;
    const headersDefecto = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${this.token}`,
      ...opciones.headers, // Permite sobrescribir o añadir headers
    };

    const respuesta = await fetch(urlCompleta, {
      ...opciones, // Fusionamos las opciones pasadas
      headers: headersDefecto,
    });

    if (!respuesta.ok) {
      // Intentamos obtener un mensaje de error del cuerpo de la respuesta
      const errorDetalle = await respuesta.json().catch(() => ({})); // Si no es JSON, obtenemos un objeto vacío
      throw new Error(errorDetalle.mensaje || `Error HTTP ${respuesta.status}`);
    }

    return respuesta.json(); // Devolvemos la respuesta parseada
  }

  // Métodos de conveniencia
  get(ruta) { return this.peticion(ruta); }
  post(ruta, datos) { return this.peticion(ruta, { method: 'POST', body: JSON.stringify(datos) }); }
  put(ruta, datos) { return this.peticion(ruta, { method: 'PUT', body: JSON.stringify(datos) }); }
  delete(ruta) { return this.peticion(ruta, { method: 'DELETE' }); }
}

// Ejemplo de uso:
const miApi = new ClienteApi('https://api.ejemplo.com', 'miTokenSecreto123');
const todosLosUsuarios = await miApi.get('/usuarios');
const nuevoUsuarioCreado = await miApi.post('/usuarios', { nombre: 'Carlos' });

Errores Comunes a Evitar

  • Olvido de la comprobación response.ok: Las respuestas de error HTTP (4xx, 5xx) no lanzan excepciones automáticamente. Debes verificar response.ok para detectarlas.
  • Establecer manualmente Content-Type para FormData: Al enviar datos de formulario (archivos, etc.), el navegador necesita generar un delimitador único (boundary). Dejar que el navegador lo haga es crucial.
  • Consumir el cuerpo de la respuesta múltiples veces: Métodos como response.json() o response.text() solo pueden ser llamados una vez por respuesta. Almacena el resultado en una variable si necesitas usarlo varias veces.
  • No manejar AbortError: Cuando se utiliza AbortController, la promesa rechazada tendrá la propiedad name igual a 'AbortError'. Es importante capturar y manejar este caso específico.

¡Prueba Nuestras Herramientas Gratuitas para Desarrolladores!

Formatea respuestas de API y codifica parámetros de solicitud de manera sencilla.