Axios 错误模型
AxiosError 用 response、request 与 setup/config path 区分“收到非成功响应”“请求已发但未收到响应”“请求在发出前或处理阶段失败”;validateStatus 决定哪些 HTTP 状态进入 rejection,但业务错误契约仍应与 Axios transport error 分层。
[!info] related notes
- 所属 MOC: HTTP 与前端网络请求
- 上位概念: Axios
- 请求配置: Axios 请求配置
- 响应结构: Axios 响应结构
- 拦截器: Axios 拦截器
- 实践: 前端 HTTP 状态码处理, 稳定 API 错误模型
Axios 错误模型
一句话定义
Axios 的错误首先应该按请求生命周期发生在哪一层判断,而不是只看 status code。
最常用三分:
error.response
→ 收到了 HTTP response,但按 validateStatus 被判定为失败
error.request
→ request 已创建/发出,但没有拿到 response
neither
→ setup / config / interceptor / serialization 等阶段失败
AxiosError
Axios rejection 通常得到 AxiosError。
常见字段:
messagenamestackconfigcodestatus(在适用版本/场景中)requestresponse
其中真正重要的是:
先判断 response 是否存在,再判断 request 是否存在。
1. error.response
说明:
request
→ server / intermediary
→ HTTP response returned
→ Axios classified response as rejection
典型:
- 400
- 401
- 403
- 404
- 409
- 422
- 500
默认情况下,Axios 通常只把 2xx 视为 fulfilled;具体边界由 validateStatus 决定。
此时可以读取:
error.response.status
error.response.data
error.response.headers
有 response 不等于“网络没问题”
它只能说明 HTTP response 到了 Axios。
仍可能是:
- 网关错误页;
- API 业务错误;
- stale proxy;
- auth/session 问题;
- server validation failure。
2. error.request
说明 Axios 已创建/发送请求,但没有获得可用 response。
浏览器环境中 request 常对应 XMLHttpRequest;Node adapter 下可能是 ClientRequest 等底层对象。
常见原因:
- DNS / connection failure;
- timeout;
- connection reset;
- CORS 导致浏览器不给脚本暴露 response;
- server 没有返回;
- request 被中止;
- proxy/network failure。
所以:
error.request 不等于“服务端一定没收到请求”。
尤其对于非幂等操作:
POST /payments
→ server may have committed
→ response lost
→ client only sees network failure
这时不能盲目自动重试。
3. setup / config path
当:
!error.response
&& !error.request
通常说明失败发生在真正 HTTP response 之前的客户端处理路径。
可能包括:
- invalid config;
- interceptor throw;
- adapter/setup error;
- request transformation / serialization error;
- 代码自身抛错。
不要统一包装成“Network Error”。
validateStatus
validateStatus 决定:
哪些 HTTP status 会让 Axios Promise resolve,哪些会 reject。
例如:
axios.get('/resource', {
validateStatus: status => status < 500,
})
这样 404 也可能进入 fulfilled path。
所以:
HTTP status semantics
≠ Axios Promise fulfilled/rejected semantics
后者受 validateStatus 控制。
Transport Error 与 Business Error
这是最重要的工程边界之一。
例如 API 返回:
HTTP/1.1 200 OK
Content-Type: application/json
{
"ok": false,
"code": "INSUFFICIENT_BALANCE"
}
Axios 会认为 HTTP 请求成功。
但业务仍失败。
反过来:
HTTP/1.1 409 Conflict
Axios 默认会 reject,但这可能是业务上完全可预期的 state conflict。
因此应用层应该区分:
transport / HTTP failure
vs
domain / business failure
不要把 AxiosError 当作整个系统的唯一 error model。
Interceptor 的影响
response interceptor 可以:
- normalize error;
- refresh token;
- retry;
- map API error;
- 添加 telemetry。
但 interceptor 也可能制造新的错误:
original AxiosError
→ interceptor throws another Error
→ original context lost
因此封装时应保留:
- original cause;
- status;
- request id;
- API error code;
- retryability。
Cancellation
用户主动取消:
AbortController
→ request aborted
应该与:
- timeout;
- offline;
- server 500;
区分。
取消通常不是需要用户看到“网络错误”的 failure。
toJSON()
AxiosError 的 toJSON() 可以生成更适合日志序列化的结构。
但日志仍要注意:
- Authorization;
- Cookie;
- request body 中的 secret;
- PII。
不要把完整 config 无脑上传 telemetry。
推荐处理顺序
catch(error)
→ is AxiosError?
→ cancelled?
→ response?
→ request?
→ setup/client error?
→ map to app error contract
→ decide retry / user message / telemetry
常见误区
所有 catch 都叫网络错误
错误。4xx/5xx、CORS、timeout、业务错误、取消都不是同一类。
error.request = 服务端没收到
不能推出。
status 200 = 业务成功
不能推出。
interceptor 自动 retry 所有请求
非幂等 POST 等操作可能产生重复副作用。
学习检查
error.response与error.request分别证明了什么?- 为什么
error.request不能推出“服务端没有执行”? validateStatus改变的是 HTTP status 还是 Axios Promise semantics?- 为什么业务 error contract 不应该直接等同于 AxiosError?
- 取消、timeout、500、409 为什么应该被不同处理?
- response interceptor 为什么需要保留 original cause?
- 给一个支付 POST 超时场景,解释为什么不能仅凭 Axios network error 自动重试。