Hay dos formas de realizar una solicitud HTTP en JavaScript, sobre las que se discute sin cesar, y el debate suele centrarse en los aspectos menos importantes.
El tamaño del paquete, la elegancia sintáctica, si una dependencia está justificada… todo eso son preferencias, y personas razonables pueden tener opiniones diferentes. Hay una diferencia que no es una cuestión de preferencia y que genera errores reales en el código de producción: fetch no rechaza su promesa cuando el servidor devuelve un estado de error. Un 404 se resuelve. Un 500 se resuelve. Tu .catch() nunca se ejecuta y el código continúa como si todo hubiera funcionado.
Se trata de un comportamiento documentado, más que de una peculiaridad, y es lo primero que hay que entender antes que nada.
Somos Geonode y vendemos proxies, así que aquí va una nota relevante para el tema: existe una diferencia real y poco documentada entre ambos en lo que respecta a la compatibilidad con proxies en Node, y esto suele pillar desprevenidos a los usuarios. El comando nativo «fetch» de Node no detecta las variables de entorno estándar de los proxies, lo que sorprende a casi todo el mundo que da por hecho que se comporta como «curl». Esto tiene su propia sección, y si no estás enrutando las solicitudes a través de un proxy, puedes saltártela por completo —la mayoría de la gente debería hacerlo—.
Una pequeña nota práctica, ya que afecta a cualquiera que siga enlaces antiguos: la documentación de Axios se ha trasladado. axios-http.com ahora redirige a axios.rest. Los marcadores y las respuestas de Stack Overflow que apuntan al dominio antiguo siguen funcionando a través de la redirección, pero la ubicación canónica ha cambiado.
Todo lo que se indica a continuación sobre el comportamiento procede de MDN y de la propia documentación de Axios, y no de los recuerdos de nadie.
La diferencia que provoca errores reales
Empieza por aquí, porque es la única diferencia que no es una cuestión de gustos.
Lo que dice MDN
Directamente de la documentación:
«Una promesa de tipo fetch()
solo se rechaza cuando la solicitud falla, por ejemplo, debido a una URL de solicitud mal formada o a un error de red. Una promesa fetch()
no se rechaza si el servidor responde con códigos de estado HTTP que indican errores (404
, 504
, etc.). En su lugar, un controlador then()
debe comprobar las propiedades Response.ok
y/o Response.status
».
El énfasis en no es propio de MDN.
Qué significa esto en la práctica
// Looks correct. Is not.
try {
const res = await fetch('/api/user/999');
const user = await res.json();
showUser(user); // runs on a 404
} catch (err) {
showError(err); // never runs on a 404
}
El servidor devolvió un 404 con un cuerpo de error. fetch
se resolvió sin problemas. res.json()
analizó el objeto de error. showUser
recibió algo que no es un usuario, y el fallo sale a la luz más tarde, en otro lugar, como un error confuso sobre una propiedad no definida.
La versión correcta:
try {
const res = await fetch('/api/user/999');
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const user = await res.json();
showUser(user);
} catch (err) {
showError(err);
}
Tres líneas más, y son necesarias en todas y cada una de las solicitudes que escribas.
Cómo se comporta Axios
Axios rechaza por defecto los códigos de estado fuera del rango 2xx. El código equivalente no necesita comprobación de estado:
try {
const { data } = await axios.get('/api/user/999');
showUser(data);
} catch (err) {
showError(err); // runs on a 404
}
¿Qué comportamiento es el correcto?
Ambos son defendibles, y el desacuerdo es filosófico.
fetch
adopta la postura de que la transferencia se ha realizado con éxito: se ha conectado con el servidor, este ha respondido y la respuesta ha llegado intacta. Un 404 es una respuesta válida a una pregunta, no un fallo a la hora de formularla. Rechazarla confundiría un fallo de transporte con la semántica de la aplicación. Esta es, por cierto, exactamente la misma postura que adopta curl, donde un 404 también genera un código de salida cero.
Axios sostiene que la mayoría de los usuarios que realizan llamadas consideran un 4xx o un 5xx como un fallo, por lo que debería comportarse como tal.
La realidad práctica es que el modelo de fetch
es más correcto y más propenso a errores, ya que requiere disciplina en cada llamada y nada te avisa cuando se incumple esa disciplina.
Qué es Fetch y cuánto cuesta
El estándar, con sus ventajas y sus carencias.
Las ventajas
Está integrado de serie. Sin dependencias, sin instalación, sin costes de paquetes, sin cadena de suministro que auditar. En los navegadores y en Node moderno, simplemente está ahí.
Es un estándar. Está especificado, en lugar de ser mantenido por un proyecto que podría cambiar de rumbo, quedar abandonado o introducir un cambio incompatible según su propio calendario.
Es la base. Muchas bibliotecas de nivel superior son envoltorios a su alrededor, por lo que comprender fetch significa comprender lo que hacen.
Gestiona bien el streaming. Response.body es un flujo legible, lo que hace que el procesamiento progresivo de respuestas de gran tamaño resulte natural.
Lo que no hace
Estas son las lagunas que debes cubrir tú mismo, y su número es el verdadero argumento a favor de Axios.
No hay JSON automático. Se llama a .json(), lo que supone otro «await» y otro punto en el que puede fallar si la respuesta no es JSON —una página de error HTML, por ejemplo—, que es exactamente lo que se obtiene de un servidor mal configurado.
No hay rechazo por estado. Ya se ha comentado anteriormente.
No hay tiempo de espera por defecto. Un fetch puede quedarse colgado indefinidamente. AbortSignal.timeout() lo proporciona en entornos modernos, pero hay que activarlo manualmente y es fácil olvidarse de ello —lo cual es más importante precisamente en aquellas situaciones en las que un bloqueo es lo peor que puede pasar.
No hay interceptores. No hay ningún lugar donde adjuntar un token de autenticación, un ID de correlación o un registro centralizado. O bien cada llamada lo repite, o bien hay que escribir un envoltorio, y escribir un envoltorio es precisamente cómo la gente acaba creando sin querer su propio Axios, pero peor.
No hay progreso de subida. El progreso de descarga es posible a través del flujo de respuesta; el de subida no es tan sencillo.
No hay serialización automática del cuerpo de la solicitud. Tienes que llamar a JSON.stringify y establecer tú mismo el tipo de contenido, cada vez.
No hay configuración de proxy en Node. Tiene su propia sección más abajo.
El resumen sincero
fetch es una primitiva de bajo nivel bien diseñada. Sus omisiones son deliberadas: un estándar debe ser minimalista y neutral.
La cuestión no es si es bueno. Es si quieres implementar tú mismo la capa que falta, y si la versión que implementes será mejor que la mantenida por un proyecto con una amplia base de usuarios que detecta sus casos extremos.
Qué te ofrece Axios
Según su propia documentación, la lista de características es el argumento de venta.
Cliente HTTP basado en promesas con una interfaz coherente en todos los entornos, que ofrece paquetes independientes para navegador y Node.
Interceptores para solicitudes y respuestas, que es la característica más valiosa y la más difícil de replicar de forma limpia. Basta con añadir un encabezado de autenticación una vez, gestionar la actualización 401 una vez y añadir el registro una vez, y todas las solicitudes de la aplicación lo heredan.
Gestión automática de JSON en ambas direcciones. Los cuerpos de las solicitudes se serializan y se establece el tipo de contenido; las respuestas se analizan en un objeto response.data.
Gestión de errores con rechazo ante estados distintos de 2xx.
Configuración del tiempo de espera, descrita en la documentación como una medida para evitar que las solicitudes se cuelguen indefinidamente. Un único valor de configuración en lugar de un controlador de aborto por cada llamada.
Cancelación de solicitudes en curso.
Seguimiento del progreso tanto para las subidas como para las descargas, algo que fetch no ofrece de forma directa.
Protección XSRF integrada.
Envío de archivos y datos de formularios multiparte gestionados automáticamente.
Limitación de velocidad y regulación de solicitudes.
Instancias con valores predeterminados, de modo que se puede crear una vez un cliente configurado con una URL base, encabezados y tiempo de espera, e importarlo a cualquier lugar.
Los costes
Una dependencia. Algo que hay que instalar, mantener actualizado y auditar. En un entorno preocupado por la seguridad, eso supone un coste real, más que teórico.
Tamaño del paquete. Significativo para un frontend pequeño, insignificante para una aplicación grande o cualquier cosa del lado del servidor.
Otra abstracción que aprender, y que en ocasiones se comporta de forma diferente a la plataforma subyacente de maneras que te sorprenderán.
Una visión objetiva
Axios es, a grandes rasgos, lo que la mayoría de la gente acaba creando sobre fetch cuando necesita estas funcionalidades —excepto que ya está escrito, ya está depurado y ya gestiona los casos en los que tú no habías pensado.
Si no necesitas nada de eso, es una dependencia innecesaria.
Comparación
| fetch | Axios |
|---|
| Instalación | Integrada | npm install |
| Rechaza errores 404/500 | No | Sí |
| Análisis de JSON | Manual .json() | Automático |
| Serialización del cuerpo de la solicitud | Manual | Automático |
| Tiempo de espera | AbortSignal.timeout() | Opción de configuración |
| Interceptores | No | Sí |
| Progreso de la subida | No es sencillo | Sí |
| Progreso de la descarga | A través del flujo de respuesta | Sí |
| Cancelación | AbortController | Integrada |
| Protección XSRF | Manual | Integrada |
| Instancias con valores por defecto | No | Sí |
| Configuración de proxy en Node | No | Sí |
| Transmisión | Excelente | Más limitada |
| Coste del paquete | Cero | Pequeño, pero distinto de cero |
La misma solicitud, de ambas formas
// fetch, written correctly
const res = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ name: 'Alice' }),
signal: AbortSignal.timeout(5000)
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
// Axios
const { data } = await axios.post(
'https://api.example.com/users',
{ name: 'Alice' },
{ headers: { Authorization: `Bearer ${token}` }, timeout: 5000 }
);
Ambas son correctas. La versión de fetch tiene once líneas frente a las cinco de Axios, y la diferencia radica exclusivamente en aspectos que hay que recordar en cada llamada, en lugar de configurarlos una sola vez.
Las dos filas que marcan la diferencia
Rechazos por 404/500 y la configuración del proxy en Node son las únicas filas en las que una herramienta no puede hacer directamente lo que hace la otra. El resto es código repetitivo, y el código repetitivo supone un coste, pero no una imposibilidad.
El «impuesto» del código repetitivo
La comparación real no es entre una llamada a fetch
y otra a axios
, sino entre un código fuente que utilice cada una de ellas.
Lo que todo el mundo acaba escribiendo
Después de repetir por tercera o cuarta vez la comprobación de estado, el tiempo de espera y el análisis de JSON, acabas escribiendo esto:
async function request(url, options = {}) {
const res = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options.headers
},
signal: options.signal ?? AbortSignal.timeout(options.timeout ?? 10000)
});
if (!res.ok) {
const body = await res.text();
throw new HttpError(res.status, body);
}
return res.status === 204 ? null : res.json();
}
Se trata de un envoltorio razonable y bastante bueno. También es, sin lugar a dudas, un pequeño Axios.
Lo que el envoltorio no gestionará hasta que alguien se dé cuenta
Reintentos con retroceso. Actualización del token ante un error 401 sin que se produzca una avalancha de peticiones cuando fallan varias llamadas a la vez. Peticiones que devuelven HTML en lugar de JSON. Respuestas 204 sin cuerpo. Cancelación propagada a través de llamadas anidadas. Progreso de la subida. Tipos de contenido distintos de JSON. ID de correlación para el rastreo.
Cada uno es una pequeña adición. En conjunto, forman una biblioteca, y la versión que escribas estará menos probada que la que ya están utilizando miles de personas.
Cuándo es adecuado escribirlo tú mismo
Cuando solo necesitas una pequeña parte. Un puñado de peticiones GET a una API. El envoltorio anterior tiene treinta líneas y es totalmente tuyo.
Cuando el tamaño del paquete realmente importa. Una página en la que el rendimiento es fundamental y cada kilobyte cuenta.
Cuando las dependencias son costosas. Entornos en los que cada paquete requiere revisión.
Cuando quieres comprender la plataforma. Una razón legítima, y ese conocimiento se transfiere.
Cuándo no lo es
Cuando vas por la tercera iteración del envoltorio, cuando diferentes partes del código tienen envoltorios distintos, o cuando estás añadiendo lógica de reintentos. En ese momento, estás manteniendo una biblioteca como proyecto paralelo, y la dependencia que has evitado resulta más barata que la que has creado.
El tamaño del paquete y la cuestión de los nodos
Los dos factores contextuales que influyen en la respuesta.
En el navegador
Axios aumenta el peso de tu paquete. Que eso importe o no depende totalmente de lo que estés desarrollando.
Una página de destino o un widget en los que el tiempo de carga es el producto: utiliza fetch. Cada kilobyte cuenta, y los patrones de solicitud suelen ser lo suficientemente sencillos como para que el código repetitivo resulte trivial.
Una aplicación grande que ya incluye un marco de trabajo y una biblioteca de componentes: el coste marginal es insignificante, y la compatibilidad con los interceptores vale mucho más que los bytes.
La verdad es que el tamaño del paquete es el argumento al que recurre la gente cuando busca una razón técnica para justificar una preferencia estética. Es un factor real, pero resulta decisivo con mucha menos frecuencia de lo que se suele citar.
En Node
El cálculo es totalmente diferente. El tamaño del paquete es irrelevante en un servidor, por lo que el principal argumento en contra de Axios se desvanece.
fetch, nativo de Node, está disponible en las versiones modernas de Node y funciona bien. Pero el código del lado del servidor suele necesitar precisamente aquello que omite fetch: tiempos de espera para todo, reintentos con retroceso, gestión centralizada de la autenticación, registro estructurado de las llamadas salientes y —como se explica en la siguiente sección— configuración de proxy.
Así pues, la balanza se inclina hacia Axios en el servidor más que en el navegador, lo cual es justo lo contrario de lo que suele argumentarse.
El compromiso del que nadie habla
Puedes usar ambas. fetch para llamadas sencillas, Axios cuando necesites la maquinaria. Nada lo prohíbe y el argumento de la coherencia es más débil de lo que parece.
Lo que realmente causa problemas son tres envoltorios propios diferentes en una misma base de código, cada uno de los cuales gestiona los errores de forma ligeramente distinta. Eso es peor que utilizar cualquiera de las dos bibliotecas de forma coherente, y es en lo que acaban un número sorprendente de proyectos.
Proxies en ambos
Nuestro terreno, y el origen de una tarde verdaderamente confusa para muchos desarrolladores.
La versión resumida
El fetch nativo de Node no lee las variables de entorno estándar de proxy. Establecer HTTP_PROXY y HTTPS_PROXY no tiene ningún efecto, a diferencia de curl, de la mayoría de las bibliotecas HTTP y de lo que casi todo el mundo espera.
El fetch global de Node se basa en undici, y el enrutamiento a través de un proxy requiere especificar explícitamente un «dispatcher»:
import { ProxyAgent, setGlobalDispatcher } from 'undici';
setGlobalDispatcher(new ProxyAgent('http://user:pass@proxy.example.com:8080'));
// now fetch goes through the proxy
const res = await fetch('https://example.com');
O bien, por cada solicitud, pasando la opción dispatcher.
Axios en Node
Axios cuenta con una opción de configuración proxy:
const res = await axios.get('https://example.com', {
proxy: {
protocol: 'http',
host: 'proxy.example.com',
port: 8080,
auth: { username: 'user', password: 'pass' }
}
});
Para proxies SOCKS, o para un control más preciso, el enfoque habitual consiste en utilizar una biblioteca de agentes pasada como httpAgent y httpsAgent.
En el navegador, ninguno de los dos puede
Vale la pena mencionarlo porque ahorra tiempo. JavaScript en un navegador no puede configurar un proxy. El navegador utiliza lo que haya configurado el sistema o una extensión, y ninguna biblioteca puede cambiar eso. La opción proxy de Axios es una característica de Node.
Si necesitas que las solicitudes se realicen a través de un proxy desde código basado en el navegador, la solicitud tiene que pasar por un servidor que controles.
La pista de depuración
Si un proxy «no funciona» en Node, comprueba qué cliente está realizando la solicitud antes de comprobar cualquier otra cosa. El código que funciona con Axios y falla con fetch —o viceversa— casi siempre se debe a esto, y pasa desapercibido porque ninguno de los dos genera errores. La solicitud simplemente se envía directamente.
Compruébalo solicitando un punto final de informe de direcciones a través de tu cliente configurado y verificando que la dirección devuelta es la del proxy.
Y la parte que va en contra de nuestros propios intereses
La mayoría de las llamadas HTTP del lado del servidor no necesitan ningún proxy. Llamar a una API para la que tienes credenciales, desde un servidor autorizado para acceder a ella, no requiere nada adicional: un proxy añade latencia, un punto de fallo y un coste. Los proxies tienen su razón de ser para la verificación geográfica y para la recopilación a gran escala, donde se aplican límites de velocidad por dirección. Fuera de ese contexto, el cliente sin proxy es la mejor opción.
Cuál elegir
Una lista de criterios de decisión, más que una conclusión definitiva.
Utiliza fetch cuando
El tamaño del paquete sea realmente crítico. Páginas de destino, widgets, scripts incrustados.
Tus solicitudes sean sencillas. Unas pocas peticiones GET, gestión mínima de errores, sin autenticación compartida.
No puedes añadir dependencias, o cada paquete requiere revisión.
Estás trabajando con flujos. El modelo de streaming de fetch es mejor y esto supone una ventaja técnica real, más que una simple preferencia.
Quieres aprender la plataforma. Los conocimientos son transferibles; los específicos de Axios, no.
Utiliza Axios cuando
Necesites interceptores. Autenticación centralizada, actualización de tokens, registro de eventos, ID de correlación. Esta es la razón más importante y no tiene un equivalente claro en fetch.
Estés en el servidor. El tamaño del paquete no es un problema y las características que faltan son precisamente las que necesita el código del servidor.
Necesitas seguir el progreso de la subida, algo que fetch no facilita.
Estás realizando muchas solicitudes variadas en una base de código extensa y quieres un comportamiento coherente sin tener que mantener un envoltorio.
Necesitas configuración de proxy y prefieres utilizar una opción documentada en lugar de montar un distribuidor.
Elijas lo que elijas
Establece siempre un tiempo de espera. AbortSignal.timeout() o la opción timeout de Axios. Una solicitud sin tiempo de espera puede quedarse colgada indefinidamente, y este es el fallo de fiabilidad más común en el código HTTP de JavaScript.
Comprueba siempre el estado con fetch. En cada llamada, sin excepción. «res.ok» son dos palabras, y su ausencia es el error con el que comenzaba este artículo.
Centralízalo. Un envoltorio o una instancia de Axios configurada. Tres enfoques incoherentes en una misma base de código son peores que cualquiera de las dos bibliotecas, y es el resultado que nadie elige deliberadamente.
Preguntas frecuentes
¿Cuál es la principal diferencia entre Axios y fetch?
La gestión de errores. Según MDN, una promesa de «fetch» «no se rechaza si el servidor responde con códigos de estado HTTP que indican errores»; debes comprobar tú mismo response.ok. Axios rechaza los códigos de estado que no sean 2xx. Todo lo demás es una cuestión de comodidad: Axios añade interceptores, JSON automático, tiempos de espera, progreso y configuración de proxy.
¿Sigue siendo necesario Axios ahora que fetch está integrado?
Depende de lo que necesites. Para solicitudes sencillas, no. Para interceptores, progreso de subida, valores predeterminados por instancia o configuración de proxy en Node, Axios sigue ofreciendo cosas que fetch no ofrece, y escribirlas tú mismo implica mantener una pequeña biblioteca.
¿Por qué fetch no lanza una excepción ante un error 404?
Porque la transferencia se ha realizado con éxito: se ha conectado con el servidor y este ha respondido. fetch trata un 404 como una respuesta válida en lugar de como una solicitud fallida, y solo rechaza las solicitudes en caso de errores de red o de una URL mal formada. Comprueba response.ok en cada llamada.
¿Qué es más rápido, Axios o fetch?
Para una sola solicitud, la diferencia es insignificante; ambos dependen de la red. Axios añade una pequeña cantidad de procesamiento y, en el navegador, una pequeña descarga de la propia biblioteca.
¿Cómo configuro un tiempo de espera con fetch?
AbortSignal.timeout(5000) Se pasa como la opción signal en entornos modernos, o mediante AbortController con tu propio temporizador. No hay un tiempo de espera predeterminado, por lo que una solicitud sin él puede quedarse colgada indefinidamente.
¿Funciona fetch con proxies en Node?
No a través de las variables de entorno habituales. La variable global fetch de Node se basa en undici y no lee HTTP_PROXY ni HTTPS_PROXY. Debes proporcionar un distribuidor de ProxyAgent, ya sea de forma global con setGlobalDispatcher o por solicitud. Axios tiene, en su lugar, una opción de configuración proxy.
¿Puedo utilizar un proxy con fetch en el navegador?
No. El JavaScript del navegador no puede configurar un proxy; el navegador utiliza la configuración del sistema o de las extensiones. La opción de proxy de cualquier biblioteca es una característica exclusiva de Node. Las solicitudes con proxy desde el navegador deben pasar por un servidor que controles.
¿Debería utilizar tanto Axios como fetch en un mismo proyecto?
En sí mismo, no supone ningún problema. Lo que causa verdaderos problemas es la existencia de varios envoltorios propios inconsistentes con comportamientos de error diferentes. La coherencia en la forma de gestionar los errores es más importante que la biblioteca que haya generado la solicitud.
Conclusión
Una diferencia es de fondo y el resto es cuestión de preferencias.
fetch no rechaza las solicitudes en caso de errores HTTP. MDN lo indica explícitamente: un error 404 o 504 se resuelve, y debes comprobar tú mismo response.ok o response.status. Si se pasa por alto esto en una llamada, el fallo se manifiesta en un lugar completamente distinto, como un error confuso sobre datos que nunca fueron válidos. Axios rechaza los códigos que no sean 2xx por defecto. Ambas posturas son defendibles; solo una requiere disciplina en cada llamada, y la disciplina no es una cualidad que los equipos de desarrollo mantengan cuando tienen plazos que cumplir.
Todo lo demás depende de lo que estés desarrollando. En el navegador, fetch está integrado y es gratuito, y para patrones de solicitud sencillos, el código repetitivo es trivial. En el servidor, el tamaño del paquete deja de importar y las características que omite fetch —tiempos de espera, interceptores, reintentos, configuración de proxy— son precisamente lo que necesita el código del servidor, por lo que la balanza se inclina hacia el otro lado.
La prueba que vale la pena aplicar: si has escrito un envoltorio alrededor de fetch y ahora tiene más de unas treinta líneas, estás manteniendo una pequeña biblioteca HTTP. Esa es una elección legítima tomada deliberadamente y una mala elección tomada por accidente.
En cuanto a los proxies, y esta es la parte en la que la gente pierde toda una tarde: el fetch nativo de Node ignora las variables de entorno estándar de proxy. Establecer HTTPS_PROXY no tiene ningún efecto, la solicitud se envía directamente y no se produce ningún error. Proporciona un distribuidor ProxyAgent de Undici, o utiliza la opción proxy de Axios, y compruébalo con un punto final que informe de la dirección en lugar de darlo por sentado.
Vendemos proxies y la mayoría de las solicitudes del lado del servidor no necesitan ninguno. Cuando sí se necesite uno, saber qué cliente se está utilizando es el primer paso para la depuración, no el último.