跪拜 Guibai
← All articles
Frontend · Axios · JavaScript

Hands-On Test of the 'Ban Axios' Argument: Fetch Covers the Basics, but Three Unmentioned Traps Decide the Migration

By Flynt ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The cost of dropping axios isn't in the feature comparison; it's the silent 404/500 behavior and the refresh race that will surface as production incidents. Those two alone turn an otherwise clean migration into a full regression testing exercise.

Summary

A direct line-by-line test of the 'ban axios' claims against axios 1.20.0 and Node's native fetch on v22.23.2 confirms timeout and cancellation behave almost identically: a 500ms limit on a 3-second endpoint cut off at 517ms with axios and 504ms with fetch, with different error types (AxiosError vs TimeoutError). CancelToken still works but throws CanceledError and is deprecated.

The real differences surface in three unmentioned places. Request interceptors execute in reverse registration order, so three handlers registered A, B, C run C, B, A; that stack order breaks any handler that depends on a previous one's output. More critically, axios rejects on HTTP errors while fetch resolves with res.ok false, so existing catch-based error toasts never fire on a 404 or 500, and undefined data flows into rendering until the page white-screens. The costliest trap is a race in the common token-refresh pattern: a single global refreshing variable prevents duplicate refresh calls only when 401s arrive in the same event loop tick. When they spread across ticks, a second 401 sees refreshing cleared and triggers another refresh; on the same logic, axios's interceptor chain spread the five 401s across async boundaries and fired three refreshes while fetch fired one. The fix is to capture the token used for the request and compare it before re-fetching, which brings both clients down to a single refresh.

Takeaways
— axios 1.20.0 is the current latest; Node v22.23.2's fetch was used for comparison.
— On a 3-second endpoint, a 500ms timeout cuts off at 517ms with axios and 504ms with fetch; error types differ (AxiosError vs TimeoutError).
— CancelToken is deprecated but still functional in 1.20.0; it throws CanceledError instead of AxiosError.
— Request interceptors execute in reverse registration order (stack), response interceptors in registration order (queue).
— axios rejects on HTTP 4xx/5xx with ERR_BAD_REQUEST; fetch resolves with res.ok false, leaving status handling to the caller.
— A global refreshing variable in token refresh logic prevents duplicate refreshes only when 401s arrive in the same event loop tick; spread arrivals cause multiple refreshes (3 vs 1 in the test).
— Capture the token at request time and compare before re-fetching to guarantee exactly one refresh per batch of 401s.
Conclusions

The reverse interceptor order is a documented behavior that feels like a bug; it explains many 'impossible' debugging sessions where a logging interceptor sees undefined.

The semantic difference on HTTP errors is the real migration blocker: existing catch blocks are written for network failures, so business errors vanish silently.

The refresh race is library-agnostic; common open-source templates carry it, and it only becomes visible under certain timing patterns.

The test itself shows that micro-benchmarks can mislead: the same fetch logic produced one refresh in one timing and two in another, depending on how 401 responses are batched.

Concepts & terms
AbortSignal.timeout
A Web API that wraps an AbortController in a timer, letting fetch cancel after a given number of milliseconds; when triggered, it throws a TimeoutError.
Single-flight pattern
A concurrency pattern where all callers of an operation share a single in-flight promise, so only one underlying request is made until that promise settles.
Interceptor stack vs queue
In axios, request interceptors run in reverse registration order (a stack) while response interceptors run in registration order (a queue); this asymmetry causes surprising dependency failures.
ERR_BAD_REQUEST
An axios error code for any HTTP 4xx/5xx response; it differs from fetch's resolution model, where the request resolves and you must inspect res.ok.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗