Axios 项目解读
项目信息
- 项目名称:Axios
- 项目描述:Axios 是一个跨运行时(Browser + Node + React Native + Bun + Deno)的 Promise-based HTTP 客户端,通过"能力匹配适配器 + 严格分离 core/I/O + 拦截器 LIFO/FIFO + 统一错误码"四件套,把跨端 API 碎片化、取消语义双轨、配置合并安全等棘手问题用克制的工程决策解决。
- 项目地址:https://github.com/axios/axios
1. 项目概览
1.1 项目定位与核心价值
一句话定位:Axios 是一个跨运行时(Browser + Node.js + React Native + Bun + Deno)的 Promise-based HTTP 客户端库,通过适配器模式在多端提供统一的"请求 / 响应 / 拦截器 / 错误 / 取消"API。
解决的核心痛点:
- 跨端 API 碎片化:浏览器侧 XHR/Fetch、Node 侧 http/https、React Native 侧又有自己的网络栈——开发者需要写多套代码。axios 通过能力匹配的 adapter 抽象抹平差异。
- 请求生命周期管理缺失:原生 fetch/XHR 没有标准的"拦截器 / 转换链 / 统一错误模型"。axios 提供 InterceptorManager + AxiosError 错误码体系 + transformRequest/transformResponse 把横切关注点(鉴权头注入、CSRF、重试、错误归一化)变成可插拔组件。
- 取消语义不统一:旧
CancelToken与新AbortSignal在不同生态中各占一边。axios 同时支持两者且不破坏任一路径,适配旧代码的渐进式迁移。 - TypeScript 与多模块形态兼容:源是 ESM(
type: module),但下游可能是 CJS/UMD/Bun/Deno/React-Native。exports条件字段 + Rollup 矩阵保证任意入口形态都可用。
目标用户/开发者画像:
- 前端工程师:在 React/Vue/Svelte 等 SPA 中调用 REST API,需要拦截器统一注入 token、统一错误处理。
- Node.js 后端工程师:使用 SSR、BFF、CLI 工具、爬虫、代理聚合服务,需要稳定 HTTP 客户端与流式上传能力。
- 全栈/SDK 作者:需要为多种运行时(浏览器 + Node + React Native + Bun + Deno)发布一致的 SDK 体验。
- 测试 / 工具开发者:通过
getAdapter/mergeConfig暴露的能力,构造 mock 适配器或自定义重试逻辑。
1.2 目标场景与典型 Case
场景一:单页应用的统一 API 客户端
- 通过
axios.create({ baseURL })派生实例;通过interceptors.request.use(...)注入 token;通过interceptors.response.use(...)归一化错误并弹 toast。 - 通过
validateStatus灵活控制哪些 HTTP 状态码算"成功"(业务方常将 4xx 视作业务错误而非异常)。
- 通过
场景二:Node 服务的文件上传 / 代理 / 重定向跟随
- 使用 HTTP 适配器,支持 FormData / 流式上传(
formDataToStream),支持https-proxy-agent代理,支持follow-redirects跟随重定向。 maxRedirects、maxBodyLength、maxContentLength控制边界。
- 使用 HTTP 适配器,支持 FormData / 流式上传(
场景三:请求取消与路由切换防抖
- 浏览器侧:使用
AbortController.signal在路由切换时中止未完成请求,dispatchRequest 阶段同步检查 + 适配器侧移除监听(避免泄漏)。 - Node 侧:使用旧
CancelToken或signal取消进行中的流式上传/下载。
- 浏览器侧:使用
场景四:渐进式替换底层
- 通过
axios.getAdapter(['xhr', 'fetch'])让库能力匹配:若浏览器支持 fetch,优先用 fetch(更现代、Stream 原生);否则回退 XHR。 - 第三方 SDK 作者可注入自定义 adapter(如 gRPC-Web bridge、Electron IPC)。
- 通过
典型使用 Case(伪代码)
javascript
// Case 1: 浏览器单页应用
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
validateStatus: (s) => s >= 200 && s < 300 || s === 304,
});
api.interceptors.request.use((config) => {
config.headers.set('Authorization', `Bearer ${getToken()}`);
return config;
});
api.interceptors.response.use(
(r) => r.data,
(err) => {
if (err.response?.status === 401) logout();
return Promise.reject(err);
}
);
await api.get('/user/me');
// Case 2: Node 端上传 + 取消
const ctrl = new AbortController();
axios.post('https://api.example.com/upload', formData, {
signal: ctrl.signal,
maxBodyLength: 100 * 1024 * 1024,
onUploadProgress: (e) => console.log(e.loaded / e.total),
});
setTimeout(() => ctrl.abort(), 5000);1.3 核心技术亮点
| 亮点 | 说明 |
|---|---|
| 适配器能力匹配 | 不依赖环境名硬编码,按能力真假值选择 |
| 拦截器 LIFO/FIFO | 请求拦截器后注册先执行;响应拦截器先注册先执行;支持 synchronous 优化 |
| 统一错误码 | 14 个 ERR_* 常量 + AxiosError.from() 包装器 |
| Header 标准化 | AxiosHeaders 大小写不敏感 + 动态注入链式 accessor |
| 配置合并安全 | Object.create(null) + 显式过滤 __proto__/constructor/prototype |
| 取消双轨并存 | CancelToken + AbortSignal 同时支持 |
| 包形态兼容 | ESM 源 + Rollup 多形态产物(CJS/UMD/Browser/Node) |
1.4 技术栈与选型对比
| 类别 | 技术 | 备注 |
|---|---|---|
| 源模块系统 | ESM (type: module) | 显式 .js 后缀导入 |
| 打包器 | Rollup | 多形态产物:ESM/UMD/CJS × browser/node |
| 构建编排 | Gulp | clear / version 任务 |
| 测试框架 | Vitest | 单元/浏览器/headless 三 project |
| 浏览器自动化 | Playwright | Vitest 浏览器 project 驱动 |
| Lint / 规范 | ESLint v10(扁平) + Prettier + lint-staged + commitlint | 强制 130 字符 commit header |
| Git 钩子 | husky | 由 npm rebuild husky && npx husky 激活 |
| 依赖完整性 | lockfile-lint | 校验 HTTPS host + integrity |
| Node 端网络 | follow-redirects / form-data / https-proxy-agent / proxy-from-env | 4 个运行时依赖 |
| 类型系统 | TypeScript(仅类型检查) | index.d.ts / index.d.cts |
| 文档站 | VitePress | 多语言:英文 + 中文 + 法语 + 西班牙语 |
选型对比决策点
- A. 适配器模式 vs 运行时分支:能力匹配 + 平台层
package.json替换 → 核心保持纯净、扩展点开放 - B. ESM + CJS 双形态:现代工具链 ESM 优先 + 老生态 CJS 兜底
- C. AxiosError 独立体系:消费方写一次错误处理就能跨平台工作
- D. 拦截器 LIFO/FIFO + 同步选项:性能与表达力兼得
- E. 配置合并 own-prop 严格判定:默认安全,避免常见 footgun
- F. 取消语义双轨:向后兼容与现代化同时推进
2. 整体架构设计
2.1 架构概述
Axios 采用分层 + 适配器 + 中间件链的混合架构,分为四层:
- 用户接口层 (Entry):
index.js→lib/axios.js,负责实例化、暴露方法别名。 - 核心域逻辑层 (Core):
lib/core/*,包含 Axios 类、调度、配置合并、Header 容器、错误、拦截器、状态判定等与 I/O 无关的域逻辑。 - 平台抽象层 (Platform):
lib/platform/{browser,node,common},通过package.json的browser/react-native字段在打包时静态替换,再由lib/platform/index.js聚合运行时类引用(FormData/URLSearchParams/Blob)。 - I/O 适配层 (Adapters):
lib/adapters/{xhr,http,fetch}.js,真正发起网络请求;lib/adapters/adapters.js提供能力匹配的getAdapter入口。
辅助层(Helpers/Tools/Defaults/Cancel/Env)横向服务于上述四层,提供可独立复用的工具函数。
2.2 整体架构图
text
+================================================================+
| 用户接口层 (Entry Layer) |
| +-------------------+ +--------------------------------+ |
| | index.js (ESM) | -> | lib/axios.js | |
| | (default + named)| | (createInstance + 静态属性) | |
| +-------------------+ +---------------+----------------+ |
| | (Axios instance) |
+================================================================+
| 核心域逻辑层 (Core Domain - I/O 无关) |
| +-----------------+ +-----------------+ +----------------+ |
| | Axios.js | | mergeConfig.js | | AxiosHeaders.js| |
| | - request() | | - 过滤 __proto__| | - 大小写不敏感 | |
| | - method 别名 | | - headers 深度 | | - normalize() | |
| +-----------------+ +-----------------+ +----------------+ |
| +-----------------+ +-----------------+ +----------------+ |
| | dispatchRequest | | InterceptorMgr | | AxiosError.js | |
| | - 取消检测 | | - LIFO/FIFO | | - 14 个 ERR_* | |
| | - transform | | - synchronous | | - from() 包装 | |
| +-----------------+ +-----------------+ +----------------+ |
| +-----------------+ +-----------------+ |
| | settle.js | | buildFullPath | |
| | - validateStatus| | - baseURL+url | |
| +-----------------+ +-----------------+ |
+================================================================+
| 辅助层 (Helpers / Defaults / Cancel / Env) |
| +-----------------+ +-----------------+ +----------------+ |
| | defaults/ | | cancel/ | | env/data.js | |
| | - transform链 | | - CancelToken | | - VERSION | |
| | - validateStatus| | - CanceledError | +----------------+ |
| +-----------------+ +-----------------+ |
| +--------------------------------------------------------------+|
| | helpers/ (36 个,通用工具:bind/buildURL/parseHeaders/...) ||
| | utils.js (953 行:KindOf/Type/merge/extend 等) ||
| +--------------------------------------------------------------+|
+================================================================+
| 平台抽象层 (Platform Abstraction) |
| +-----------------+ +-----------------+ +----------------+ |
| | browser/ | | node/ | | common/ | |
| | - index.js | | - index.js | | - utils.js | |
| | - classes/ | | - classes/ | | (kindOf 增强) | |
| | Blob,FormData | | FormData,URL | +----------------+ |
| +-----------------+ +-----------------+ |
| 静态替换策略: package.json browser 字段 → |
| ./lib/adapters/http.js -> ./lib/helpers/null.js |
| ./lib/platform/node/index.js -> ./lib/platform/browser/... |
+================================================================+
| I/O 适配层 (Adapters) |
| +-------------------------------------------------------------+|
| | adapters.js ||
| | - knownAdapters = { http, xhr, fetch } ||
| | - getAdapter(['xhr','http','fetch']) ← 能力匹配 ||
| +--------+----------------+---------------------+-------------+|
| | | | |
| +--------v-------+ +-----v-------+ +----------v-------+ |
| | http.js | | xhr.js | | fetch.js | |
| | - Node HTTP/ | | - XHR | | - Fetch API | |
| | HTTPS/HTTP2 | | - 浏览器 | | - 流 + AbortSig | |
| | - 1325 行 | | - 227 行 | | - 552 行 | |
| | - 代理/重定向/ | | | | | |
| | FormData/流 | | | | | |
| +----------------+ +-------------+ +------------------+ |
+================================================================+分层职责说明
| 层级 | 关键模块 | 职责 |
|---|---|---|
| 用户接口层 | index.js、lib/axios.js | 默认实例工厂、命名导出、静态属性挂载 |
| 核心域逻辑层 | lib/core/* | 请求派发、配置合并、Header 标准化、错误、拦截器、状态判定(无 I/O) |
| 平台抽象层 | lib/platform/* | 跨平台类引用、协议白名单差异处理 |
| I/O 适配层 | lib/adapters/* | XHR / HTTP / Fetch 三大适配器 + 能力匹配选择 |
| 辅助层 | lib/utils.js、lib/helpers/*、lib/defaults/*、lib/cancel/*、lib/env/* | 通用工具、默认配置、取消语义、运行时元数据 |
2.3 目录结构
shell
axios/
├── AGENTS.md # 【配置】AI/人类贡献者协作规范(架构边界/命名约定/错误码/拦截器顺序/安全准则)
├── CLAUDE.md # 【配置】Claude Code 项目入口(引用 AGENTS.md)
├── CHANGELOG.md # 【配置】发布历史(release-owned,由发版时维护)
├── COLLABORATOR_GUIDE.md # 【配置】维护者协作指南
├── CONTRIBUTING.md # 【配置】贡献者指南
├── CONTRIBUTORS.md # 【配置】贡献者名单
├── CODE_OF_CONDUCT.md # 【配置】社区行为准则
├── ECOSYSTEM.md # 【配置】生态与官方插件列表
├── LICENSE # 【配置】MIT 许可证
├── MIGRATION_GUIDE.md # 【配置】v0.x → v1.x 迁移指南
├── PRE_RELEASE_CHANGELOG.md # 【配置】未发布变更记录
├── PRE_RELEASE_DOCS.md # 【配置】未发布文档待办
├── README.md # 【配置】英文 README
├── README-CN.md # 【配置】中文 README(国际化基线,本次新增)
├── SECURITY.md # 【配置】安全策略
├── THREATMODEL.md # 【配置】威胁建模文档
│
├── index.js # 【核心基建】ESM 顶层入口(unwrap axios default + named exports)
├── index.d.ts # 【配置】TypeScript 类型定义(739 行)
├── index.d.cts # 【配置】TypeScript CJS 类型(export = axios)
│
├── lib/ # 【核心基建】源码主目录
│ ├── axios.js # 【核心基建】默认实例工厂(createInstance + 静态属性挂载)
│ ├── utils.js # 【工具集】通用工具聚合(953 行:Object/Type/KindOf 判定/合并/扩展)
│ │
│ ├── core/ # 【核心基建】axios 域逻辑(不依赖具体 I/O)
│ │ ├── Axios.js # 【核心基建】Axios 类(请求派发入口、method 别名、拦截器链、headers 合并)
│ │ ├── AxiosError.js # 【核心基建】标准化错误类 + 错误码常量(ERR_*) + from() 包装器 + redact
│ │ ├── AxiosHeaders.js # 【核心基建】Header 容器(大小写不敏感 + 解析 + 规范化 + accessor)
│ │ ├── InterceptorManager.js # 【核心基建】拦截器注册/移除/遍历(use/eject/clear/forEach)
│ │ ├── dispatchRequest.js # 【核心基建】请求调度(取消检测 → headers 转换 → adapter → 响应转换)
│ │ ├── mergeConfig.js # 【核心基建】配置合并(安全敏感:过滤 __proto__/constructor/prototype)
│ │ ├── buildFullPath.js # 【核心基建】URL 全路径构建(baseURL + url + allowAbsoluteUrls)
│ │ ├── transformData.js # 【核心基建】请求/响应数据转换链
│ │ ├── settle.js # 【核心基建】状态判定(validateStatus → resolve/reject)
│ │ └── README.md # 【配置】core 模块说明
│ │
│ ├── adapters/ # 【核心基建】I/O 适配器
│ │ ├── adapters.js # 【核心基建】能力探测与适配器选择(getAdapter 能力匹配)
│ │ ├── xhr.js # 【核心基建】浏览器 XMLHttpRequest 适配器(227 行)
│ │ ├── http.js # 【核心基建】Node.js http/https/http2 适配器(1325 行:重定向/FormData/流/代理/HTTP2 sessions)
│ │ ├── fetch.js # 【核心基建】Fetch API 适配器(552 行:进度模拟/流/AbortSignal/Cookies)
│ │ └── README.md # 【配置】adapters 说明
│ │
│ ├── cancel/ # 【核心基建】取消语义(双轨:CancelToken + AbortSignal)
│ │ ├── CancelToken.js # 【核心基建】传统取消令牌(subscribe + throwIfRequested + toAbortSignal)
│ │ ├── CanceledError.js # 【核心基建】取消错误类(extends AxiosError,带 __CANCEL__ 标记)
│ │ └── isCancel.js # 【核心基建】取消判定工具
│ │
│ ├── defaults/ # 【核心基建】默认配置
│ │ ├── index.js # 【核心基建】默认配置(transformRequest/transformResponse/validateStatus/headers)
│ │ └── transitional.js # 【核心基建】transitional 默认值(向后兼容行为开关)
│ │
│ ├── env/ # 【核心基建】运行时元数据
│ │ ├── data.js # 【核心基建】version 注入(gulp version 生成,勿手改)
│ │ ├── README.md # 【配置】env 说明
│ │ └── classes/ # 【工具集】跨平台类引用(URLSearchParams 等 polyfill 入口)
│ │
│ ├── helpers/ # 【工具集】通用工具(尽量可独立于 axios 复用)
│ │ ├── AxiosTransformStream.js # 【工具集】请求体 transform 流封装
│ │ ├── AxiosURLSearchParams.js # 【工具集】URLSearchParams 包装(带迭代/排序)
│ │ ├── Http2Sessions.js # 【工具集】HTTP/2 会话复用池
│ │ ├── HttpStatusCode.js # 【工具集】标准 HTTP 状态码枚举
│ │ ├── ZlibHeaderTransformStream.js # 【工具集】zstd 头检测 transform
│ │ ├── bind.js # 【工具集】Function.prototype.bind 替代(保留 arguments)
│ │ ├── buildURL.js # 【工具集】URL + params + serializer 拼接
│ │ ├── callbackify.js # 【工具集】Promise → 回调风格转换
│ │ ├── combineURLs.js # 【工具集】baseURL + path 拼接
│ │ ├── composeSignals.js # 【工具集】AbortSignal 组合(任一触发即 abort)
│ │ ├── cookies.js # 【工具集】浏览器 cookie 读写
│ │ ├── deprecatedMethod.js # 【工具集】方法弃用提示包装
│ │ ├── estimateDataURLDecodedBytes.js # 【工具集】data: URL 字节数估算
│ │ ├── formDataToJSON.js # 【工具集】FormData → JSON 转换
│ │ ├── formDataToStream.js # 【工具集】FormData → Node stream
│ │ ├── fromDataURI.js # 【工具集】data: URL 解析
│ │ ├── isAbsoluteURL.js # 【工具集】绝对 URL 判定
│ │ ├── isAxiosError.js # 【工具集】AxiosError 判定
│ │ ├── isURLSameOrigin.js # 【工具集】同源判定(XSRF 配套)
│ │ ├── null.js # 【工具集】空模块占位(browser 字段下替换 http.js)
│ │ ├── parseHeaders.js # 【工具集】header 字符串解析
│ │ ├── parseProtocol.js # 【工具集】URL 协议解析
│ │ ├── progressEventReducer.js # 【工具集】进度事件节流/聚合 + 装饰器
│ │ ├── readBlob.js # 【工具集】Blob 读取工具
│ │ ├── resolveConfig.js # 【工具集】请求/响应配置解析
│ │ ├── sanitizeHeaderValue.js # 【工具集】Header 值清洗(防 CRLF 注入)+ toByteStringHeaderObject
│ │ ├── shouldBypassProxy.js # 【工具集】noProxy 判定
│ │ ├── speedometer.js # 【工具集】速率采样(带宽限流)
│ │ ├── spread.js # 【工具集】数组参数展开
│ │ ├── throttle.js # 【工具集】流式限流
│ │ ├── toFormData.js # 【工具集】JS 对象 → FormData
│ │ ├── toURLEncodedForm.js # 【工具集】JS 对象 → URL-encoded 字符串
│ │ ├── trackStream.js # 【工具集】流式进度追踪
│ │ ├── validator.js # 【工具集】config 校验断言(assertOptions + spelling)
│ │ └── README.md # 【配置】helpers 说明
│ │
│ └── platform/ # 【核心基建】平台差异抽象
│ ├── index.js # 【核心基建】平台选择入口(导出 common utils + 运行时 classes)
│ ├── common/
│ │ └── utils.js # 【工具集】跨平台通用工具(kindOf 增强等)
│ ├── browser/
│ │ ├── index.js # 【核心基建】浏览器平台入口(默认协议白名单等)
│ │ └── classes/ # 【工具集】浏览器环境类引用占位(FormData/URLSearchParams/Blob)
│ └── node/
│ ├── index.js # 【核心基建】Node 平台入口(protocols 扩展)
│ └── classes/ # 【工具集】Node 环境类引用(FormData/URLSearchParams 走 form-data 包)
│
├── docs/ # 【配置】文档站(VitePress 源码,多语言 zh/fr/es)
│ ├── .vitepress/ # 【配置】VitePress 配置与主题
│ ├── pages/ # 【配置】英文文档页
│ │ ├── getting-started/ # 【配置】入门
│ │ ├── advanced/ # 【配置】进阶
│ │ └── misc/ # 【配置】杂项
│ ├── zh/ # 【配置】中文文档
│ ├── fr/ # 【配置】法语文档
│ ├── es/ # 【配置】西班牙语文档
│ ├── data/ # 【配置】文档数据
│ ├── patches/ # 【配置】文档依赖补丁
│ ├── public/ # 【配置】静态资源
│ ├── scripts/ # 【配置】文档脚本
│ ├── package.json # 【配置】文档子包
│ └── index.md # 【配置】文档首页
│
├── examples/ # 【业务模块】示例代码(按用例分目录)
│ ├── server.js # 【配置】示例 server
│ ├── network_enhanced.js # 【业务模块】网络增强示例
│ ├── improved-network-errors.md # 【配置】增强错误说明
│ ├── abort-controller/ # 【业务模块】AbortController 用例
│ ├── all/ # 【业务模块】axios.all 用例
│ ├── amd/ # 【业务模块】AMD 加载
│ ├── get/ # 【业务模块】GET 用例
│ ├── post/ # 【业务模块】POST 用例
│ ├── postMultipartFormData/ # 【业务模块】多部分表单用例
│ ├── transform-response/ # 【业务模块】响应转换用例
│ └── upload/ # 【业务模块】上传用例
│
├── sandbox/ # 【业务模块】sandbox(本地试运行)
│
├── scripts/ # 【工具集】仓库维护脚本
│ └── axios-build-instance.js # 【工具集】构建配置样例生成
│
├── tests/ # 【质量保证】测试目录(runtime-first 布局)
│ ├── README.md # 【配置】测试说明
│ ├── setup/ # 【工具集】Vitest setup
│ │ ├── server.js # 【工具集】本地 HTTP server(务必 try/finally 清理)
│ │ └── browser.setup.js # 【工具集】浏览器测试 setup
│ ├── unit/ # 【质量保证】单元测试(vitest)
│ │ ├── api.test.js # 【质量保证】API 测试
│ │ ├── axios.test.js # 【质量保证】Axios 类测试
│ │ ├── axiosHeaders.test.js # 【质量保证】AxiosHeaders 测试
│ │ ├── prototypePollution.test.js # 【质量保证】原型污染回归
│ │ ├── adapters/ # 【质量保证】适配器测试
│ │ └── ... # 【质量保证】其他单元测试
│ ├── browser/ # 【质量保证】浏览器测试(需 Playwright)
│ ├── smoke/ # 【质量保证】打包后烟囱测试
│ │ ├── cjs/ # 【质量保证】CJS 烟囱
│ │ ├── esm/ # 【质量保证】ESM 烟囱
│ │ ├── bun/ # 【质量保证】Bun 烟囱
│ │ └── deno/ # 【质量保证】Deno 烟囱
│ └── module/ # 【质量保证】模块兼容性测试
│ ├── cjs/ # 【质量保证】CJS + TS 4.9
│ └── esm/ # 【质量保证】ESM + TS 5.x
│
├── gulpfile.js # 【配置】gulp 任务(clear / version / build 编排)
├── rollup.config.js # 【配置】Rollup 配置(多形态:ESM/UMD/CJS × browser/node)
├── vitest.config.js # 【配置】Vitest 配置(unit/browser/browser-headless 三个 project)
├── eslint.config.js # 【配置】ESLint v10 扁平配置
├── tsconfig.json # 【配置】TypeScript 配置
├── tslint.json # 【废弃】旧 TSLint 配置(保留兼容)
├── webpack.config.js # 【配置】Webpack 配置(sandbox 演示)
├── package.json # 【配置】npm 元数据(exports/dependencies/scripts)
├── package-lock.json # 【配置】锁文件(lockfile-lint 校验)
└── .npmrc # 【配置】npm 配置(ignore-scripts=true)3. 模块依赖与调用关系
3.1 全局入口与核心路由
请求入口分两条:
- 静态方法(如
axios.get()):index.js暴露的default导出其实是lib/axios.js创建的实例(createInstance(defaults))。调用axios.get(url, config)实际是Axios.prototype.get闭包调用this.request(config)。 - 实例方法(如
axios.create().get()):用户在createInstance闭包内通过instance.create(instanceConfig)派生新实例,新实例继承了Axios.prototype上的方法。
request() 是真正的总入口 → 内部进入 _request() 完成 config 合并 + headers 合并 + 拦截器链构建 → dispatchRequest() 选中 adapter → adapter 发起 I/O → 响应回到响应拦截器链 → resolve/reject。
3.2 调用拓扑
text
index.js (ESM 顶层入口)
└── default = axios
└── lib/axios.js createInstance(defaults)
├── const context = new Axios(defaults)
│ ├── this.defaults = defaults
│ └── this.interceptors = { request, response } (两个 InterceptorManager)
├── const instance = bind(Axios.prototype.request, context)
├── utils.extend(instance, Axios.prototype, context, { allOwnKeys: true })
│ -> instance.{get,post,put,patch,delete,head,options,request,...}
├── utils.extend(instance, context, null, { allOwnKeys: true })
│ -> instance.{defaults, interceptors}
├── instance.create(instanceConfig) = createInstance(mergeConfig(defaults, instanceConfig))
└── 静态挂载: Axios / AxiosError / CanceledError / CancelToken / isCancel
/ VERSION / toFormData / isAxiosError / AxiosHeaders
/ formToJSON / getAdapter / HttpStatusCode / all / spread
/ mergeConfig / default = axios
Axios.prototype.request(configOrUrl, config) [lib/core/Axios.js:39-80]
└── await this._request(...) (async 包装,补全错误栈)
└── _request(configOrUrl, config) [lib/core/Axios.js:82-233]
├── 1. config = mergeConfig(this.defaults, config)
├── 2. 校验 transitional / paramsSerializer
├── 3. config.method = (config.method || this.defaults.method || 'get').toLowerCase()
├── 4. headers = AxiosHeaders.concat(common[method], headers)
├── 5. 构建 requestInterceptorChain (LIFO,过滤 runWhen)
├── 6. 构建 responseInterceptorChain (FIFO)
├── 7. (synchronousRequestInterceptors 优化路径)
│ while (i < len) newConfig = onFulfilled(newConfig) // 同步执行
│ promise = dispatchRequest.call(this, newConfig)
│ while (i < len) promise = promise.then(...) // 响应链
└── 8. (默认异步路径)
chain = [dispatchRequest.bind(this), undefined, ...responseInterceptors]
chain.unshift(...requestInterceptors)
promise = Promise.resolve(config)
while (i < len) promise = promise.then(chain[i++], chain[i++])
dispatchRequest(config) [lib/core/dispatchRequest.js:34-89]
├── throwIfCancellationRequested(config) // 取消检测
├── config.headers = AxiosHeaders.from(config.headers) // 标准化
├── config.data = transformData.call(config, transformRequest) // 请求体转换
├── config.headers.setContentType('application/x-www-form-urlencoded', false) // post/put/patch
├── adapter = adapters.getAdapter(config.adapter || defaults.adapter, config) // 能力匹配
└── adapter(config).then(
onAdapterResolution => throwIfCancellationRequested + transformResponse + AxiosHeaders.from
onAdapterRejection => if (!isCancel) throwIfCancellationRequested + transformResponse
)
adapters.getAdapter(adapters, config) [lib/adapters/adapters.js:65-115]
├── adapters = Array.isArray ? adapters : [adapters]
├── for i in adapters:
│ ├── nameOrAdapter = adapters[i]
│ ├── if !isResolvedHandle: adapter = knownAdapters[id.toLowerCase()]
│ ├── if adapter && (typeof fn === function || (adapter = adapter.get(config))): break
└── return adapter (或抛 AxiosError ERR_NOT_SUPPORT)
knownAdapters: [lib/adapters/adapters.js:16-22]
- http: import httpAdapter from './http.js' (Node 端)
- xhr: import xhrAdapter from './xhr.js' (浏览器端,导出时已 capability-check)
- fetch: { get: fetchAdapter.getFetch } (能力检测后再 get)3.3 核心业务实体与关联
text
[Axios Instance] 1 -------> 1 [InterceptorManager (request)]
| \
| 1 -------> 1 [InterceptorManager (response)]
|
| 1 -------> 1 [defaults (config snapshot)]
|
| 1 -------> N [Interceptor Handler]
| |
| +-- { fulfilled, rejected, synchronous, runWhen }
|
| 1 -------> 1 [AxiosHeaders (request)]
| 1 -------> 1 [AxiosHeaders (response)]
|
| 1 -------> 1 [Config (per-request)]
|
+-- url, method, baseURL, headers, data, params
+-- timeout, signal, cancelToken, validateStatus
+-- transformRequest[], transformResponse[]
+-- adapter (string|function|array)
+-- onUploadProgress, onDownloadProgress
+-- httpAgent, httpsAgent, maxContentLength, ...
[AxiosError] 1 <-------> 1 [CancelToken | AbortSignal]
|
+-- name = 'AxiosError', isAxiosError = true
+-- code (14 个常量: ERR_NETWORK, ECONNABORTED, ETIMEDOUT, ERR_FR_TOO_MANY_REDIRECTS, ...)
+-- config, request, response (可选)
[CancelToken] 1 <-------> 1 [CanceledError (extends AxiosError)]
|
+-- reason (Cancel 调用时填充)
+-- _listeners (订阅列表)
+-- toAbortSignal() (与 AbortSignal 互转)
[Adapter] 1 <-------> 1 [Request/Response Pipeline]
|
+-- xhr -> XMLHttpRequest
+-- http -> http.ClientRequest / https.ClientRequest / http2 session
+-- fetch -> fetch API4. 核心模块详解
模块一:Axios 请求调度器 (lib/core/Axios.js + lib/core/dispatchRequest.js)
- 模块名称:
Axios+dispatchRequest - 设计说明:双层调度。
Axios._request()负责拦截器链构建;dispatchRequest()负责请求体转换 + adapter 选择 + 响应体转换。两者解耦后,_request关心"链",dispatchRequest关心"内容"。 - 拦截器顺序控制:通过
transitional.legacyInterceptorReqResOrdering切换新旧行为(LIFO 推入 vs 旧版 push)。 - 同步优化:当所有请求拦截器都声明
synchronous: true时,跳过 Promise.then 链,性能与可读性兼得。 - 关键安全:错误处理
request()async 包装补全 stack 链(避免 Node 异步调用丢失栈帧)。
内部结构图 :
text
+--------------------------+
| Axios.prototype.request | (async 包装,补全 stack)
+-----------+--------------+
|
v
+--------------------------+
| Axios.prototype._request|
+------+----------+--------+
| |
| | headers (concat common[method] + user headers)
| v
| +------------------+
| | AxiosHeaders |
| +------------------+
|
| build requestInterceptorChain (LIFO)
| build responseInterceptorChain (FIFO)
|
v
+----------------------------------------------------+
| chain = [dispatchRequest.bind(this), undefined, |
| ...requestInterceptors, ...responseInterceptors] |
| promise = Promise.resolve(config) |
| while (i < len) promise = promise.then(chain[i++],|
| chain[i++])|
+--------------------+-------------------------------+
|
v
+--------------------------------------+
| dispatchRequest(config) |
| - throwIfCancellationRequested |
| - AxiosHeaders.from(headers) |
| - transformData(transformRequest) |
| - getAdapter(...) |
| - adapter(config) |
| - .then(transformResponse + 取消再检)|
+--------------------------------------+模块二:适配器能力匹配 (lib/adapters/adapters.js)
- 模块名称:
adapters适配器注册表与能力匹配 - 设计说明:不依赖环境名,靠能力真假值判定。
http永远是已加载的httpAdapter函数(Node 端),但浏览器端package.json把http.js替换为helpers/null.js→ 导入的是null。xhr在导入时立即 capability-check:typeof XMLHttpRequest !== 'undefined',否则导出false。fetch是{ get: getFetch }对象,get(config)内部再次运行时检查typeof fetch === 'function'。
getAdapter策略:遍历传入数组,第一个能成功解析(函数 or 对象.get 返回真值)的胜出;全失败抛AxiosError('There is no suitable adapter...', 'ERR_NOT_SUPPORT')。- 关键安全:
Object.defineProperty(fn, 'name', { __proto__: null, value })使用 null-proto 描述符,防止被污染的Object.prototype.name干扰。
text
adapters.getAdapter(adapters, config)
+-- isResolvedHandle = utils.isFunction || === null || === false
+-- forEach adapter:
| +-- if isResolvedHandle: 直接用
| +-- else: knownAdapters[name.toLowerCase()]
| +-- if undefined: throw AxiosError("Unknown adapter " + id)
+-- 全部失败: throw AxiosError("There is no suitable adapter...", ERR_NOT_SUPPORT)模块三:Header 标准化 (lib/core/AxiosHeaders.js)
- 模块名称:
AxiosHeaders大小写不敏感 Header 容器 - 设计说明:用
Symbol-keyed内部状态($internals),键名归一化到小写,但保留原始大小写写入(构建时formatHeader可选归一)。accessor()静态方法动态为常用 header 注入getXxx/setXxx/hasXxx链式方法。 - 关键安全:
buildAccessors使用__proto__: null描述符,防止被污染的Object.prototype.get转 accessor。 - 可迭代协议:
Symbol.iterator+Symbol.toStringTag让它看起来像原生容器;toJSON()/toString()让它能直接喂给 XHR/Fetch/JSON。
text
+---------------------------+
| AxiosHeaders |
+---------------------------+
- 内部: Object 实例,键归一化为小写
- 静态 accessor: Content-Type/Content-Length/Accept/Accept-Encoding/User-Agent/Authorization
- 构造: new AxiosHeaders(rawHeaders) -> set(rawHeaders)
- 实例方法: set/get/has/delete/clear/normalize/concat/toJSON/toString/getSetCookie
- 静态: from(thing) / concat(first, ...targets) / accessor(header)模块四:配置合并 (lib/core/mergeConfig.js)
- 模块名称:
mergeConfig配置安全合并 - 设计说明:
- 结果对象用
Object.create(null),并用Object.defineProperty还原hasOwnProperty为不可枚举的 own slot。 - 显式
for循环遍历合并键,遇到__proto__/constructor/prototype直接 return(防 Prototype Pollution)。 mergeMap表驱动:每种 config 字段指定合并策略(valueFromConfig2/defaultToConfig2/mergeDirectKeys/mergeDeepProperties)。transitional.validateStatusUndefinedResolves配合config2.validateStatus === undefined的处理:a8e4f13修复点。
- 结果对象用
- 关键安全:每一步都用
utils.hasOwnProp严格 own-prop 判定;headers 合并用caseless: true走大小写不敏感路径。
text
mergeConfig(config1, config2)
+-- config = Object.create(null) + restore hasOwnProperty
+-- mergeMap = { url/method/data: valueFromConfig2,
| baseURL/...: defaultToConfig2,
| validateStatus: mergeDirectKeys,
| headers: (a, b) => mergeDeepProperties(headersToObject(a), headersToObject(b), true) }
+-- forEach prop in (Object.keys({...config1, ...config2})):
| +-- skip __proto__/constructor/prototype
| +-- a = ownProp(config1, prop), b = ownProp(config2, prop)
| +-- config[prop] = mergeMap[prop] || mergeDeepProperties
+-- transitional.validateStatusUndefinedResolves 特殊处理
+-- return config模块五:取消语义 (lib/cancel/*)
- 模块名称:
CancelToken+CanceledError+isCancel - 设计说明:
CancelToken基于 Promise + 监听者模式;executor接收cancel(message, config, request)回调,调用时构造CanceledError并 resolve。- 支持链式:每个
then都被包装成可独立cancel的派生 promise。 toAbortSignal()把旧式CancelToken转AbortSignal(迁移桥)。- 与
AbortSignal并存:dispatchRequest同时检查config.cancelToken与config.signal;adapter 内部在done()中清理 listener 防泄漏。
text
CancelToken
+-- constructor(executor) - executor 接收 cancel(msg, config, request)
+-- throwIfRequested() - 同步检测,已取消则抛 CanceledError
+-- subscribe(listener) - 注册监听
+-- unsubscribe(listener) - 反注册
+-- toAbortSignal() - 转 AbortSignal(迁移桥)
+-- static source() - 工厂方法,返回 { token, cancel }
CanceledError extends AxiosError
+-- __CANCEL__ = true (作为 isCancel 判定依据)
isCancel(value) - value?.__CANCEL__ === true5. 关键数据流程
场景一:简单 JSON GET 请求
场景二:拦截器链(含 LIFO + 异步)
场景三:取消请求(双轨并存)
6. 接口与契约规范
6.1 核心内部模块契约 (TypeScript Interfaces)
typescript
/**
* Axios 实例核心契约
*/
export interface IAxiosInstance {
(config: AxiosRequestConfig): AxiosPromise;
(url: string, config?: AxiosRequestConfig): AxiosPromise;
defaults: AxiosRequestConfig;
interceptors: {
request: InterceptorManager<AxiosRequestConfig>;
response: InterceptorManager<AxiosResponse>;
};
getUri(config?: AxiosRequestConfig): string;
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
delete<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
head<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
options<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
put<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
patch<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
postForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
putForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
patchForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
create(config?: AxiosRequestConfig): AxiosInstance;
}
/**
* 请求配置契约
*/
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method;
baseURL?: string;
allowAbsoluteUrls?: boolean;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: (RawAxiosRequestHeaders & AxiosHeaders) | AxiosHeaders;
params?: any;
paramsSerializer?: ParamsSerializerOptions | CustomParamsSerializer;
data?: D;
timeout?: number;
timeoutErrorMessage?: string;
withCredentials?: boolean;
adapter?: AxiosAdapter | AxiosAdapterName | AxiosAdapterName[];
responseType?: ResponseType;
xsrfCookieName?: string;
xsrfHeaderName?: string;
xsrfHeaderValue?: string | ((config: AxiosRequestConfig) => string | undefined);
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
decompress?: boolean;
maxContentLength?: number;
maxBodyLength?: number;
beforeRedirect?: (options: { headers: AxiosHeaders; statusCode: number }) => void;
transport?: any;
httpAgent?: any;
httpsAgent?: any;
cancelToken?: CancelToken;
signal?: GenericAbortSignal;
formDataHeaderPolicy?: 'legacy' | 'content-only';
validateStatus?: ((status: number) => boolean) | null;
env?: { FormData?: typeof FormData; Blob?: typeof Blob };
maxRedirects?: number;
maxRate?: number;
socketPath?: string | null;
allowedSocketPaths?: string[];
responseEncoding?: string;
formSerializer?: FormSerializerOptions;
parseReviver?: (key: string, value: any, context: { source: string }) => any;
transitional?: TransitionalOptions;
redact?: string[];
[extra: string]: any;
}
/**
* 响应契约
*/
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText?: string;
headers: AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
/**
* 拦截器管理契约
*/
export interface InterceptorManager<V> {
use(fulfilled?: (value: V) => V | Promise<V>, rejected?: (error: any) => any, options?: { synchronous?: boolean; runWhen?: (config: AxiosRequestConfig) => boolean }): number;
eject(id: number): void;
clear(): void;
forEach(fn: (interceptor: { fulfilled: (value: V) => V | Promise<V>; rejected: (error: any) => any; synchronous: boolean; runWhen: ((config: AxiosRequestConfig) => boolean) | null }) => void): void;
}
/**
* 适配器契约
*/
export type AxiosAdapter = (config: AxiosRequestConfig) => AxiosPromise;
/**
* 错误契约
*/
export class AxiosError<T = unknown, D = any> extends Error {
constructor(message?: string, code?: string, config?: AxiosRequestConfig<D>, request?: any, response?: AxiosResponse<T, D>);
static from<T = unknown, D = any>(error: Error | string | object, code?: string, config?: AxiosRequestConfig<D>, request?: any, response?: AxiosResponse<T, D>, customProps?: object): AxiosError<T, D>;
toJSON(): object;
cause?: Error;
config?: AxiosRequestConfig<D>;
code?: string;
request?: any;
response?: AxiosResponse<T, D>;
status?: number;
isAxiosError: boolean;
name: string;
// 错误码常量
static ERR_BAD_OPTION_VALUE: string;
static ERR_BAD_OPTION: string;
static ECONNABORTED: string;
static ETIMEDOUT: string;
static ECONNREFUSED: string;
static ERR_NETWORK: string;
static ERR_FR_TOO_MANY_REDIRECTS: string;
static ERR_DEPRECATED: string;
static ERR_BAD_RESPONSE: string;
static ERR_BAD_REQUEST: string;
static ERR_CANCELED: string;
static ERR_NOT_SUPPORT: string;
static ERR_INVALID_URL: string;
static ERR_FORM_DATA_DEPTH_EXCEEDED: string;
}6.2 对外 API 契约(节选 — OpenSpec 形式)
axios 本质是客户端库,没有对外 HTTP 端点。下表是核心公开 API 入口的契约清单:
yaml
# === Axios 默认实例 ===
- name: axios
type: function
signature: "axios(configOrUrl, config?) => Promise<AxiosResponse>"
description: |
核心调用入口。支持 axios(config) 与 axios(url, config) 两种形式
(后者模拟 fetch API 风格)。
examples:
- "axios({ method: 'post', url: '/user', data: { name: 'fred' } })"
- "axios('/user/12345')"
- name: axios.get|delete|head|options
type: function
signature: "(url, config?) => Promise<AxiosResponse>"
description: 不带请求体的方法别名。
- name: axios.post|put|patch|query
type: function
signature: "(url, data?, config?) => Promise<AxiosResponse>"
description: 带请求体的方法别名。
notes: post/put/patch 还有 *Form 变体(multipart/form-data)。
- name: axios.create
type: function
signature: "(config?) => AxiosInstance"
description: 派生新实例,defaults = mergeConfig(parent.defaults, config)。
- name: axios.interceptors
type: object
properties:
request: InterceptorManager<AxiosRequestConfig>
response: InterceptorManager<AxiosResponse>
# === 静态挂载 ===
- name: Axios
type: class
description: 暴露 Axios 类以支持继承(extends Axios)。
- name: AxiosError
type: class
description: 标准错误类 + 14 个错误码常量。
factory: AxiosError.from(error, code?, config?, request?, response?, customProps?)
- name: CanceledError
type: class
description: extends AxiosError,标记 isCancel 判定。
- name: CancelToken
type: class
status: deprecated
description: |
旧式取消令牌。仍受支持(向后兼容)但新代码应使用 AbortController.signal。
factory: CancelToken.source() => { token, cancel }
methods: [subscribe, unsubscribe, throwIfRequested, toAbortSignal]
- name: isCancel
type: function
signature: "(value: any) => boolean"
description: 判定一个错误是否是 CanceledError(err.__CANCEL__ === true)。
- name: AxiosHeaders
type: class
description: 大小写不敏感的 Header 容器。
static_methods: [from(thing), concat(first, ...targets), accessor(header)]
- name: mergeConfig
type: function
signature: "(config1: AxiosRequestConfig, config2: AxiosRequestConfig) => AxiosRequestConfig"
description: |
安全合并两个 config。结果用 Object.create(null) 防原型污染,
显式过滤 __proto__/constructor/prototype。
- name: getAdapter
type: function
signature: "(adapters: AdapterName|AdapterFunction|[...], config: AxiosRequestConfig) => AdapterFunction"
description: 能力匹配第一个可用的适配器。
- name: HttpStatusCode
type: enum
description: 标准 HTTP 状态码枚举(Ok, MultipleChoices, ...)。
- name: toFormData
type: function
signature: "(obj: object, formData?: FormData, formSerializer?: FormSerializerOptions) => FormData"
description: 把 JS 对象转换为 FormData 实例。
- name: formToJSON
type: function
signature: "(form: FormData|HTMLFormElement) => object"
description: FormData → 普通对象(支持嵌套 formFields)。
- name: isAxiosError
type: function
signature: "(payload: any) => boolean"
description: 判定对象是否是 AxiosError。
- name: all
type: function
status: deprecated
description: 等价 Promise.all。
- name: spread
type: function
status: deprecated
description: 回调风格参数展开。
- name: VERSION
type: string
description: 由 gulp version 任务注入到 lib/env/data.js。7. 快速开始
7.1 环境配置
- Node.js 18+(推荐 LTS)
- npm 8+(
package-lock.json锁定) - 浏览器自动化(可选):
npx playwright install --with-deps
7.2 安装与运行
bash
# 克隆仓库
git clone https://github.com/axios/axios.git
cd axios
# 安装依赖(使用 npm ci 走 lockfile)
npm ci
# 启用 husky git hooks(首次安装后)
npm rebuild husky && npx husky
# 单元测试
npm run test:vitest:unit
# 浏览器测试
npx playwright install
npm run test:vitest:browser:headless
# 构建产物
npm run build # 产出 dist/7.3 典型用例
Case 1: 浏览器 SPA 统一 API 客户端
javascript
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
validateStatus: (s) => s >= 200 && s < 300 || s === 304,
});
api.interceptors.request.use((config) => {
// 每次请求前添加 token
config.headers.set('Authorization', `Bearer ${getToken()}`);
return config;
});
api.interceptors.response.use(
(r) => r.data,
(err) => {
if (err.response?.status === 401) logout();
return Promise.reject(err);
}
);
await api.get('/user/me');Case 2: Node 端上传 + 取消
javascript
import axios from 'axios';
const ctrl = new AbortController();
axios.post('https://api.example.com/upload', formData, {
signal: ctrl.signal,
maxBodyLength: 100 * 1024 * 1024,
onUploadProgress: (e) => console.log(e.loaded / e.total),
});
setTimeout(() => ctrl.abort(), 5000);Case 3: 自定义 Adapter
javascript
import axios from 'axios';
const instance = axios.create({
adapter: (config) => {
// 自定义 I/O:返回 Promise<AxiosResponse>
return new Promise((resolve, reject) => {
// ... 你的实现
});
},
});Case 4: 解构覆盖默认 Adapter
javascript
import axios from 'axios';
// 强制使用 fetch 适配器
const instance = axios.create({ adapter: 'fetch' });
// 或按顺序尝试
const instance2 = axios.create({ adapter: ['xhr', 'fetch'] });