¿Cuáles son las diferencias entre XMLHttpRequest y fetch() en JavaScript y en los navegadores?
Tiempo estimado de lectura: 3 min
- XHR es una API veterana orientada a eventos y callbacks; fetch() es la API moderna basada en Promesas, diseñada para integrarse con Service Workers y Streams.
- fetch() no rechaza por códigos HTTP; hay que comprobar
response.ok. XHR requiere revisarstatusenonload. - Cancelación y progreso difieren: XHR tiene
.abort()yupload.onprogress; fetch() usaAbortControllery Streams para descarga. - Compatibilidad: XHR es universal (incluido IE); fetch() es moderno y puede requerir polyfill para entornos legacy.
Introducción
Si trabajas con la red en el navegador, tarde o temprano te encontrarás con esta pregunta: ¿Cuáles son las diferencias entre XMLHttpRequest y fetch() en JavaScript y en los navegadores? La respuesta no es solo sintaxis: es arquitectura, garantías y compromisos. Aquí tienes lo que realmente importa —con ejemplos y criterio técnico— para decidir con fundamento.
En una línea: XHR es una API veterana basada en eventos y callbacks; fetch() es la API moderna basada en Promesas, diseñada para integrarse con Service Workers, Streams y async/await. Pero esa frase no resuelve bugs. Vamos por partes.
Resumen rápido (lectores con prisa)
Qué es: XHR es una API basada en eventos; fetch() es una API basada en Promesas y diseñada para la web moderna.
Cuándo usarlo: Usa fetch() por defecto en proyectos modernos; conserva XHR si necesitas upload progress o soporte legacy sin polyfills.
Por qué importa: Comportamientos sobre errores, cancelación y progreso difieren y pueden introducir bugs sutiles.
Cómo funciona (breve): XHR expone estados y callbacks; fetch() devuelve Promesas y se integra con AbortController y Streams.
Paradigma y legibilidad
XMLHttpRequest (XHR): modelo orientado a eventos. Listeners, estados (readyState), comprobaciones manuales del status. Código más verboso y propenso a anidaciones.
fetch(): devuelve una Promesa. Compatible con async/await. Composición y manejo más claro de flujos asíncronos.
Ejemplo mínimo
// XHR
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.onload = () => { if (xhr.status===200) console.log(xhr.responseText) };
xhr.send();
// fetch
const res = await fetch('/api/data');
if (!res.ok) throw new Error(res.status);
console.log(await res.text());
Manejo de errores HTTP (el detalle que rompe apps)
fetch() no rechaza la promesa por códigos HTTP 4xx/5xx. Solo rechaza por fallos de red. Debes comprobar response.ok o response.status. XHR tampoco “lanza” un error automático: ahí el patrón habitual siempre ha sido revisar xhr.status en onload. La confusión surge al emigrar sin ajustar esa validación (documentado en MDN: Using Fetch).
Cancelación de peticiones
XHR: tiene .abort() en la propia instancia.
fetch(): usa AbortController. Más explícito y reutilizable, pero añade un pequeño boilerplate.
const controller = new AbortController();
fetch('/api', { signal: controller.signal });
controller.abort(); // cancela
Progreso de subida (upload progress)
Aquí XHR mantiene ventaja práctica: xhr.upload.onprogress ofrece bytes transferidos y total, ideal para barras de progreso en subidas grandes. fetch() puede manejar progreso de descarga con ReadableStream (Streams API), pero el progreso de subida no está estandarizado de forma simple en todos los navegadores. Si tu app hace uploads pesados, XHR o una librería que lo soporte sigue siendo la opción más directa.
Streams y Service Workers
fetch() está pensado para la web moderna: integración nativa con Service Workers y la Streams API, lo que permite estrategias offline y control fino de respuesta incrementales. XHR no tiene esa integración (spec Fetch: WHATWG spec).
Cookies y credenciales (CORS)
XHR: withCredentials = true para enviar cookies en peticiones cross-origin.
fetch(): usa credentials (p. ej. credentials: 'include'). El comportamiento por defecto ha cambiado con el tiempo; sé explícito para evitar sorpresas.
Compatibilidad y polyfills
XHR es compatible con todos los navegadores (incluido IE). fetch() está disponible en navegadores modernos; para soporte legacy hay que polyfillear o usar librerías (ver MDN: XMLHttpRequest).
Tabla rápida (resumen técnico)
| Característica | XMLHttpRequest | fetch() |
|---|---|---|
| Paradigma | Eventos/callbacks | Promesas / async-await |
| Errores HTTP | Revisar status en onload |
Requiere response.ok |
| Cancelación | .abort() |
AbortController |
| Upload progress | Sí (upload.onprogress) |
No estandarizado |
| Streams / Service Workers | No | Sí |
| Compatibilidad | Universal (legacy) | Modern browsers (polyfill posible) |
Criterio práctico: ¿cuál elegir?
Elige fetch() por defecto en proyectos modernos. Mejor integración con async/await, Service Workers y arquitectura actual.
Conserva XHR (o usa una librería que lo abstraiga, como Axios) si necesitas:
- Progreso de subida preciso.
- Soporte sin polyfills para navegadores antiguos.
Usa abstracciones del ecosistema (Angular HttpClient, Axios) cuando necesites interceptores, retry policies o consistencia entre entornos; pero conoce las capas inferiores para depurar.
Recursos y lecturas
Conclusión
No es una cuestión de “moderno contra antiguo” sino de garantías. Saber qué comportamientos esperan ambas APIs (especialmente sobre errores, cancelación y progreso) te evita bugs sutiles en producción. Elijas lo que elijas, hazlo con conocimiento: ese es el criterio que diferencia código que sobrevive a equipos y tiempo del que solo sobrevive a una urgencia.
FAQ
Respuesta: ¿fetch() rechaza la promesa en errores HTTP (4xx/5xx)?
No. fetch() solo rechaza la promesa por fallos de red u errores de infraestructura. Para errores HTTP debes comprobar response.ok o response.status y manejar el flujo correspondiente.
Respuesta: ¿Cómo cancelo una petición fetch?
Usa un AbortController y pasa su señal en las opciones: fetch(url, { signal: controller.signal }). Llama a controller.abort() para cancelar.
Respuesta: ¿Puedo obtener progreso de subida con fetch()?
No de forma estandarizada en todos los navegadores. Para progreso de subida preciso sigue usando XHR (xhr.upload.onprogress) o una librería que lo soporte.
Respuesta: ¿Necesito polyfill para fetch() en producción?
Depende de tu público objetivo. Si necesitas soportar navegadores legacy (por ejemplo IE) tendrás que polyfillear fetch() o usar alternativas como XHR o Axios.
Respuesta: ¿Cuál es la ventaja de fetch() con Service Workers?
fetch() se integra nativamente con Service Workers y la Streams API, permitiendo estrategias offline, cacheo avanzado y control incremental de respuestas.
Respuesta: ¿Qué debo usar para compatibilidad con IE?
Usa XMLHttpRequest directamente o una librería/polyfill para fetch(). XHR es compatible sin polyfills en entornos legacy.

Leave a Reply