Axios 错误处理深度解析:AxiosError 结构、错误码、超时区分与敏感信息脱敏
axios 默认在请求失败时让 Promise 被 reject,而失败的具体形态由 AxiosError 对象承载。本文基于官方文档 docs/fr/pages/advanced/error-handling.md 并结合 axios 源码,完整解析 axios 抛出的错误结构(message、code、status、config、request、response 等字段)、axios 内部识别的全部错误码及其在源码中的产生位置,以及 validateStatus 自定义判定、timeout 超时错误区分(ECONNABORTED 与 ETIMEDOUT)、畸形 HTTP(S) URL 的严格校验,还有通过 toJSON() 序列化错误和使用 redact 配置避免密钥泄漏的实战方案。读完本文,你可以在浏览器与 Node.js 环境下编写结构清晰、可区分超时/网络/取消等错误类型、且不会在日志中泄露凭据的错误处理代码。
AxiosError:axios 错误的统一载体
axios 抛出的绝大多数错误都是 AxiosError 实例(由原生 Error 派生),文档给出的通用结构如下:
| 属性 | 定义 |
|---|---|
message |
错误消息与失败状态码的简短摘要。 |
name |
标识错误来源。来自 axios 的错误,该值始终为 AxiosError。 |
stack |
提供错误的调用堆栈。 |
config |
请求发起时 axios 的配置对象,包含用户为请求/实例设置的各项配置。 |
code |
代表被 axios 识别的错误类型,具体取值见下一节的错误码表。 |
status |
HTTP 响应状态码。 |
此外,AxiosError 实例还带有一个布尔标记 isAxiosError = true,这正是 axios.isAxiosError(error) 的判定依据。从源码看,isAxiosError.js 中该函数只做一次属性检查:
export default function isAxiosError(payload) {
return utils.isObject(payload) && payload.isAxiosError === true;
}
而在 AxiosError.js 的构造函数中,各字段被显式挂到实例上(第 160–168 行):
this.name = 'AxiosError';
this.isAxiosError = true;
code && (this.code = code);
config && (this.config = config);
request && (this.request = request);
if (response) {
this.response = response;
this.status = response.status;
}
可以据此推断出文档中三个错误分支与源码字段的对应关系:只有拿到服务端响应(response)时才设置 status;只有请求已发出但无响应时 request 才有值;而配置阶段的错误通常既没有 request 也没有 response,只留下 config 与 message。
axios 内部识别的错误码(code)
文档列出了 axios 可识别的全部错误码。这些错误码以静态属性形式定义在 AxiosError.js 中,可统一通过 AxiosError.ERR_NETWORK 等引用:
| 错误码 | 含义 |
|---|---|
ERR_BAD_OPTION_VALUE |
axios 配置中提供了无效或不受支持的值。 |
ERR_BAD_OPTION |
axios 配置中提供了无效的选项。 |
ECONNABORTED |
通常表示请求超时(除非设置了 transitional.clarifyTimeoutError),或被浏览器/插件中止。 |
ETIMEDOUT |
请求超过了 axios 默认超时时间。需将 transitional.clarifyTimeoutError 设为 true,否则抛出的仍是通用的 ECONNABORTED。 |
ERR_NETWORK |
网络类问题。在浏览器中,CORS 违规或混合内容(mixed content)也可能导致此错误;出于安全考虑,浏览器不允许 JS 得知真实原因,请查看控制台。 |
ERR_FR_TOO_MANY_REDIRECTS |
请求被重定向的次数超过了 axios 配置中指定的最大值。 |
ERR_DEPRECATED |
使用了 axios 中已被弃用的功能或方法。 |
ERR_BAD_RESPONSE |
响应无法被正确解析或格式不符合预期,通常对应 5xx 状态码。 |
ERR_BAD_REQUEST |
请求格式不符合预期或缺少必需参数,通常对应 4xx 状态码。 |
ERR_CANCELED |
功能或方法被用户通过 AbortSignal(或 CancelToken)显式取消。 |
ERR_NOT_SUPPORT |
当前 axios 运行环境不支持该功能或方法。 |
ERR_INVALID_URL |
axios 请求提供了无效的 URL。 |
ERR_FORM_DATA_DEPTH_EXCEEDED |
序列化 params 或表单数据时,某对象深度超过了配置的 maxDepth,默认限制 100 层。可参考 请求配置文档 中的 paramsSerializer 与 formSerializer 说明。 |
源码中可以找到几个代表性错误码的产生点:
ERR_BAD_REQUEST/ERR_BAD_RESPONSE由 settle.js 在响应判定失败时抛出,状态码 4xx/5xx 与错误码的对应关系即在此处确定:
reject(new AxiosError(
'Request failed with status code ' + response.status,
response.status >= 400 && response.status < 500 ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE,
response.config,
response.request,
response
));
ERR_INVALID_URL由 buildFullPath.js 中的assertValidHttpProtocolURL抛出(详见下文"畸形 URL"一节),此外 fromDataURI.js 在解析非法 data URI 时也会使用该错误码。ERR_FORM_DATA_DEPTH_EXCEEDED分别由 toFormData.js 与 formDataToJSON.js 在递归序列化超过maxDepth时抛出。
捕获与处理错误:response / request / config 三分支
axios 的默认行为是:请求失败时 reject Promise。捕获错误后,推荐的判断顺序是"先查 error.response,再查 error.request,最后处理配置阶段的错误"。这是文档给出的标准示例:
axios.get("/user/12345").catch(function (error) {
if (error.response) {
// 请求已发出,且服务端返回了非 2xx 的状态码
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求已发出,但未收到任何响应
// 浏览器中 `error.request` 是 XMLHttpRequest 实例,
// Node.js 中是 http.ClientRequest 实例
console.log(error.request);
} else {
// 请求配置阶段就发生了错误
console.log("Error", error.message);
}
console.log(error.config);
});
这个三分支结构之所以可靠,是因为它直接对应 AxiosError 构造函数的赋值逻辑:response 分支意味着响应已存在(服务端返回了非 2xx);request 分支意味着请求已挂到实例上但响应缺失(网络中断、超时、CORS 等);否则就是配置校验、URL 解析等问题,此时只有 config 和 message 可用。在 Node.js 环境中,http.js 适配器抛错时会把原生 http.ClientRequest 作为 request 传入;浏览器中则由 xhr.js 传入 XMLHttpRequest 实例。
使用 validateStatus 自定义"何为失败"
axios 的默认判定是 status >= 200 && status < 300 时 resolve,否则 reject。通过配置项 validateStatus 可以覆盖这一条件,自行决定哪些 HTTP 状态码应当触发错误:
axios.get("/user/12345", {
validateStatus: function (status) {
return status < 500; // 只有状态码小于 500 才视为成功
},
});
默认实现在 defaults/index.js 中定义,判定逻辑则在 settle.js 执行:若 validateStatus(response.status) 返回真值则 resolve,否则构造 AxiosError 并 reject。此外 mergeConfig.js 中还有一个 transitional.validateStatusUndefinedResolves 细节:当请求级配置显式传入 validateStatus: undefined 且该过渡开关为 false 时,会回退到实例级 validateStatus,这允许你用请求级配置覆盖实例默认值。
处理超时:ECONNABORTED 与 ETIMEDOUT 的区分
当请求超过配置的 timeout 时,axios 默认以 ECONNABORTED 拒绝 Promise。若希望获得更精确的 ETIMEDOUT 错误码,需要设置 transitional.clarifyTimeoutError: true:
async function fetchWithTimeout() {
try {
const response = await axios.get("https://example.com/data", {
timeout: 5000, // 5 秒
transitional: {
// 若希望用 ETIMEDOUT 替代 ECONNABORTED,设为 true
clarifyTimeoutError: true,
},
});
console.log("Response:", response.data);
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
console.error("Request timed out. Please try again.");
return;
}
console.error("Axios error:", error.message);
return;
}
console.error("Unexpected error:", error);
}
}
源码层面,transitional.js 显示 clarifyTimeoutError 的默认值是 false,因此不显式开启时超时一律是 ECONNABORTED。浏览器适配器的 request.ontimeout 处理(xhr.js)体现了这一切换逻辑:
reject(
new AxiosError(
timeoutErrorMessage,
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
config,
request
)
);
Node.js 适配器 http.js 中的超时处理采用完全相同的三元表达式;另外 composeSignals.js 在组合 AbortSignal 超时时直接抛出 ETIMEDOUT,可据此推断不同触发路径下错误码可能略有差异,编写兜底逻辑时建议同时兼容 ECONNABORTED 与 ETIMEDOUT。
生产环境务必设置
timeout,否则被阻塞的请求可能永远处于挂起状态。相关配置项参见 请求配置文档 中的timeout与transitional.clarifyTimeoutError说明。
畸形 HTTP(S) URL:ERR_INVALID_URL 的严格校验
axios 会拒绝 url 或 baseURL 中协议后缺少 // 的 http:/https: URL。例如 https:example.com 与 https:/example.com 不会被浏览器或 Node.js 的 URL 解析器"静默修正",而是直接抛出 code 为 ERR_INVALID_URL 的 AxiosError。请使用 https://example.com 这类格式正确的 URL。
错误消息会明确指出有问题的 URL,例如:
Invalid URL "https:example.com": missing "//" after protocol
这一行为在 buildFullPath.js 中实现,核心是一个正则与断言函数:
const malformedHttpProtocol = /^https?:(?!\/\/)/i;
function assertValidHttpProtocolURL(url, config) {
if (typeof url === 'string') {
const normalizedURL = normalizeURLForProtocolCheck(url);
if (malformedHttpProtocol.test(normalizedURL)) {
throw new AxiosError(
`Invalid URL ${JSON.stringify(redactSensitiveURLParts(normalizedURL))}: missing "//" after protocol`,
AxiosError.ERR_INVALID_URL,
config
);
}
}
}
其中 normalizeURLForProtocolCheck.js 会先对齐 WHATWG URL 的预处理规则:剔除前导空白字符并移除 \t、\n、\r 控制字符后再做协议检查,防止通过控制字符绕过校验。
安全动机:这种严格校验能阻止畸形 URL 绕过 baseURL 拼接逻辑或 URL 白名单机制。更重要的是,错误消息中对 URL 的展示做了系统性脱敏(redactSensitiveURLParts):
- 保留协议、主机、路径与查询参数名,使请求仍可被识别;
- 掩码凭据(userinfo)、查询参数值与 fragment 内容,替换为
[REDACTED ****]标记。
之所以必须"系统性"掩码,是因为 AxiosError.message 总是被 toJSON() 原样序列化进日志,而配置中的 redact 选项只能清理 config 下的键值,无法清理已经生成好的错误消息文本。单元测试 buildFullPath.test.js 验证了这一行为,例如:
'Invalid URL "https:[REDACTED ****]@api.example.com/v1?apikey=[REDACTED ****]&id=[REDACTED ****]#token=[REDACTED ****]&[REDACTED ****]": missing "//" after protocol'
序列化错误:toJSON() 与 redact 脱敏配置
使用 toJSON() 可以获得包含更多信息的错误对象快照:
axios.get("/user/12345").catch(function (error) {
console.log(error.toJSON());
});
从 AxiosError.js 的 toJSON() 实现可以看到,返回对象包含标准字段(message、name、stack)、浏览器扩展字段(description、fileName 等)以及 axios 特有字段(config、code、status)。
为了避免把密钥从 error.config 中打进日志,可以在请求配置里传入 redact 数组。调用 AxiosError#toJSON() 时,任何深度的、大小写不敏感的同名配置键都会被替换为脱敏标记:
axios.get("/user/12345", {
headers: { Authorization: "Bearer token" },
redact: ["authorization"]
}).catch(function (error) {
console.log(error.toJSON().config.headers.Authorization); // [REDACTED ****]
});
实现上,toJSON() 检测到 config.redact 是非空数组时,会调用 redactConfig(AxiosError.js)生成脱敏后的配置快照:它把 redact 中的键统一转为小写后逐一匹配,递归遍历普通对象与数组,对 AxiosHeaders 实例先调用其 toJSON() 再处理,并通过 seen 列表短路循环引用。命中键的值被替换为模块级常量 REDACTED = '[REDACTED ****]'。AxiosError.test.js 的 toJSON redaction via config.redact 测试组覆盖了完整语义:redact 未定义或为空数组时保持旧序列化行为;顶层键、嵌套对象(auth.password、proxy.auth.password)、AxiosHeaders 实例、对象数组内的键均可被正确掩码;并且对继承的 redact 访问器与原型污染场景做了防护。
小结:可落地的错误处理清单
结合文档与源码,可归纳出如下可复制的错误处理实践:
- 用
axios.isAxiosError(error)确认错误来源,再按error.response→error.request→ 其他 的顺序三分支处理; - 依赖
error.code而非error.message文本做类型分发:ERR_CANCELED(取消)、ETIMEDOUT/ECONNABORTED(超时)、ERR_NETWORK(网络/CORS)、ERR_BAD_REQUEST/ERR_BAD_RESPONSE(4xx/5xx); - 用
validateStatus精确控制哪些状态码算失败,例如把 404 视为"正常空结果"时返回status < 500; - 生产环境设置
timeout,并按需开启transitional.clarifyTimeoutError以获得可区分的ETIMEDOUT; - 日志输出统一走
error.toJSON(),并对Authorization、token、password等键配置redact;注意错误消息文本本身的脱敏只由 axios 在生成时完成(如畸形 URL 场景),redact无法回溯清理。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00