axios 功能全景:Isomorphic HTTP 客户端的核心特性与源码实现解析
本文基于 axios 官方文档(docs/pages/getting-started/features.md)整理并深度扩充,系统讲解 axios 作为 Promise 化 HTTP 客户端的全部核心能力:跨浏览器与 Node.js 的 Isomorphic 架构、可选的 Fetch 适配器、进度捕获、超时与取消、请求体自动序列化、Node.js 带宽限速、XSRF 防护等。读完本文,你不仅能知道每个特性“怎么用”,还能通过仓库源码理解每个特性“为什么这样设计”以及由哪个文件实现。
一、总览:一个 HTTP 客户端,两种运行环境
axios 的定位是“Promise based HTTP client for the browser and node.js”(见 package.json 的 description 字段)。它用一套统一的 API 覆盖了浏览器和 Node.js 两个环境下的 HTTP 请求,官方特性文档给出的能力清单包括:
- 支持 Promise API
- 拦截请求与响应
- 转换请求与响应数据
- 请求取消(Abort Controller)
- 超时(timeouts)
- 查询参数序列化,支持嵌套对象
- 请求体自动序列化为 JSON、Multipart/FormData、URL 编码表单
- 以 JSON 形式提交 HTML 表单
- 自动处理响应中的 JSON 数据
- 浏览器与 Node.js 的进度捕获,附带传输速率、剩余时间等额外信息
- Node.js 的带宽限制配置
- 兼容符合规范的 FormData 和 Blob(包括 Node.js)
- 客户端 XSRF 防护
下文按“运行环境 → 适配器 → 逐项功能特性 → 源码级原理”的顺序逐一展开。
二、Isomorphic:同一套 API 同时服务前端与后端
axios 是一个通用(universal / Isomorphic)HTTP 客户端:同一段 axios.get(url) 代码可以原样跑在浏览器里,也可以跑在 Node.js 服务端。这意味着:
- 构建 PWA、单页应用(SPA)和服务端渲染(SSR)应用时,前后端可以共用同一套请求封装;
- 前后端混合团队只需维护一份 HTTP 请求逻辑,降低代码库复杂度。
从源码结构看,这种 Isomorphism 是通过“条件导出 + 平台模块”实现的,有两层证据:
- 构建产物按环境分发:package.json 的
exports字段为不同消费环境指定了不同入口——浏览器拿到dist/browser/axios.cjs,Node.js 拿到dist/node/axios.cjs,Bun、React Native 也有各自的分支;同时browser字段会在浏览器打包时把 lib/adapters/http.js 替换为空实现 lib/helpers/null.js,即“Node 适配器在浏览器包中根本不存在”。 - 运行时平台抽象层:lib/platform/index.js 下分为 browser 与 node 两套实现,分别向核心暴露各平台原生的
FormData、Blob、URLSearchParams等类。核心逻辑(lib/core/、lib/helpers/)不直接依赖任何平台 API,而是依赖这层抽象。
默认请求实例由 lib/axios.js 的 createInstance(defaults) 创建,并挂载了 CancelToken、isCancel、AxiosError、toFormData、mergeConfig、AxiosHeaders、HttpStatusCode 等公共 API——这些正是下面各节功能特性的落点。
三、Fetch 适配器:可选启用,API 完全一致
axios 对 Fetch API 提供一等支持。Fetch 是 XHR 的现代替代,而 axios 的 fetch 适配器默认不启用,需要通过配置显式开启;关键是:无论走 XHR 还是 Fetch,对外的 axios API 保持完全一致,因此可以在不修改业务代码的前提下渐进式采用 Fetch。
3.1 适配器是怎么被选择的
所有已知适配器注册在 lib/adapters/adapters.js 中:
const knownAdapters = {
http: httpAdapter, // Node.js 的 http/https
xhr: xhrAdapter, // 浏览器的 XMLHttpRequest
fetch: { get: fetchAdapter.getFetch }, // Fetch API
};
选择逻辑在 getAdapter(adapters, config) 中:它按顺序遍历配置给出的适配器名(或自定义适配器函数),取第一个“当前环境支持”的适配器;全部不可用时抛出 AxiosError(错误信息会逐条列出每个适配器被拒绝的原因,如 “adapter xhr is not supported by the environment”)。fetch 之所以是“可选”的,是因为它的注册值是 { get: getFetch } 形式——get 会做运行时能力探测(如 lib/adapters/fetch.js 中检查 fetch、Request、Response、ReadableStream 是否存在),不支持时返回 false。
默认顺序定义在 lib/defaults/index.js:
const defaults = {
// 浏览器中优先 xhr,其次 fetch;Node.js 中 xhr 不可用,落到 http,最后 fetch
adapter: ['xhr', 'http', 'fetch'],
// ...
};
3.2 启用方式
// 方式一:按名称指定适配器(可指定单个,保证行为确定)
const res1 = await axios.get('https://httpbin.org/anything', {
adapter: 'fetch',
});
// 方式二:提供一个有序列表,按环境依次尝试
const res2 = await axios.get('https://httpbin.org/anything', {
adapter: ['fetch', 'xhr'],
});
// 方式三:提供自定义适配器函数(最高优先级)
const res3 = await axios.get('https://httpbin.org/anything', {
adapter: (config) => fetchWrapper(config),
});
也可以用 axios.getAdapter 在运行时查看/指定解析结果,该工具函数由 lib/axios.js 从 adapters 模块导出。适配器机制的完整说明见官方文档 docs/pages/advanced/adapters.md;tests/unit/adapters/adapters.test.js 与 tests/unit/adapters/fetch.test.js 分别验证了适配器的解析规则与 Fetch 适配器在各环境下的行为。
四、浏览器与 Node.js 支持情况
- 浏览器:axios 兼容所有现代浏览器及部分旧浏览器,包括 Chrome、Firefox、Safari、Edge。
- Node.js:官方文档声明兼容经过测试的 Node.js 版本一直可追溯至 v12.x,适合那些无法或不便升级 Node 版本的环境。
- 其他运行时:除 Node.js 外,仓库为 Bun 和 Deno 内置了 smoke 测试以验证关键运行时行为。在 package.json 中可以看到对应的测试脚本:
"test:smoke:deno": "deno task --cwd tests/smoke/deno test",
"test:smoke:bun": "bun test --cwd tests/smoke/bun"
测试用例分布在 tests/smoke/deno/tests/(cancel、error、fetch、headers、import 等)与 tests/smoke/bun/tests/(cancel、error、fetch、formData、headers、http、import、interceptors、progress、timeout 等),覆盖导入方式、取消、错误处理、进度事件等关键路径。浏览器端则另有独立的 browser 测试项目(npm run test:vitest:browser,见 package.json 的 scripts),测试文件位于 tests/browser/。
五、逐项功能特性与源码实现
5.1 Promise API
axios 所有请求方法都返回 Promise。批量并发直接使用原生 Promise.all(axios.all 仅是其别名):
import axios from 'axios';
const [users, cities] = await Promise.all([
axios.get('/users'),
axios.get('/cities'),
]);
响应解析由 settle 完成:状态码满足 validateStatus(默认 200 <= status < 300,见 lib/defaults/index.js)时 resolve,否则以 AxiosError reject,见 lib/core/settle.js。
5.2 拦截器(请求与响应)
实例通过 interceptors.request / interceptors.response 两个 InterceptorManager 管理钩子,实现位于 lib/core/InterceptorManager.js,在 lib/core/Axios.js 的 request() 主流程中串入。典型用法——统一附加认证头与统一错误处理:
const instance = axios.create({ baseURL: 'https://api.example.com' });
instance.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
});
instance.interceptors.response.use(
(res) => res,
(error) => {
if (axios.isAxiosError(error)) {
console.error('请求被取消或失败:', error.code);
}
return Promise.reject(error);
}
);
测试依据:tests/unit/core/Axios.test.js、tests/unit/core/InterceptorManager.test.js;深入用法见 docs/pages/advanced/interceptors.md。
5.3 请求与响应数据转换
axios 允许用 transformRequest / transformResponse 两个钩子数组在发出前/返回前改写数据,默认实现位于 lib/defaults/index.js:
const res = await axios.get('/data', {
transformResponse: [(data) => normalize(data)],
});
默认的 transformResponse 负责“自动处理响应中的 JSON”:当 responseType === 'json' 且响应体是字符串时,尝试 JSON.parse;解析失败且处于严格模式时抛出 ERR_BAD_RESPONSE 错误(见 lib/defaults/index.js 中 forcedJSONParsing / silentJSONParsing 与 transitional 相关分支)。
5.4 请求取消(Abort Controller / CancelToken)
axios 原生支持标准 AbortController,也保留了自研的 CancelToken(兼容旧代码):
import axios from 'axios';
// 标准 AbortController
const controller = new AbortController();
axios
.get('/long-running-request', { signal: controller.signal })
.catch((err) => {
if (axios.isCancel(err)) {
console.log('请求已取消:', err.message);
}
});
setTimeout(() => controller.abort(), 5000);
// 兼容旧式 CancelToken
const token = axios.CancelToken.source();
axios.get('/long-running-request', { signal: token.token });
token.cancel('用户点击了返回');
取消信号在三个适配器中都生效:lib/adapters/fetch.js 通过 composeSignals 合并 signal 并透传给 fetch 的 signal 选项;XHR 与 http 适配器分别调用 xhr.abort() / 请求对象的 abort。CanceledError、isCancel、CancelToken 的实现见 lib/cancel/ 目录,axios.isCancel 判断工具在 lib/cancel/isCancel.js。行为验证:tests/unit/cancel/canceledError.test.js、Bun/Deno 的 cancel smoke 测试;用法文档:docs/pages/advanced/cancellation.md。
5.5 超时(timeouts)
timeout 配置以毫秒为单位,默认 0 表示不启用(见 lib/defaults/index.js 的注释:“If set to 0 (default) a timeout is not created”)。超时触发后请求被中止,并以超时相关的 AxiosError reject(code 为 ECONNABORTED):
try {
await axios.get('/slow-endpoint', { timeout: 5000 });
} catch (err) {
if (axios.isAxiosError(err) && err.code === 'ECONNABORTED') {
console.log('请求超时');
}
}
5.6 查询参数序列化(支持嵌套对象)
params 会经过 lib/helpers/buildURL.js 的 buildURL(url, params, options) 拼接到 URL 上,底层由 lib/helpers/AxiosURLSearchParams.js 完成序列化,天然支持对象嵌套:
// 嵌套对象会被序列化为 a[b][c]=... 形式
await axios.get('/search', {
params: {
filter: { status: 'active', tags: ['a', 'b'] },
page: 1,
},
});
buildURL 还提供两个可选参数:encode(自定义每个值的编码,默认实现将 %3A、%24、%2C、%20 还原为 :、$、,、+,见 lib/helpers/buildURL.js)与 serialize(完全接管序列化逻辑)。边界情况(如 URL 中已带 # 或 ?)在源码中有专门处理(lib/helpers/buildURL.js)。单测:tests/unit/helpers/buildURL.test.js、tests/unit/query.test.js。
5.7 请求体自动序列化:JSON / Multipart / URL 编码
默认 transformRequest(lib/defaults/index.js)根据数据类型与 Content-Type 自动选择三种序列化格式:
| 目标格式 | 触发条件 | 源码行为 |
|---|---|---|
JSON (application/json) |
普通对象/数组,且未指定其他 Content-Type | 调用 stringifySafely(data)(内部为 JSON.stringify,若传入字符串且是合法 JSON 则原样保留),并设置 Content-Type: application/json |
URL 编码表单 (application/x-www-form-urlencoded) |
指定该 Content-Type,或传入 URLSearchParams 实例 |
走 toURLEncodedForm(data, formSerializer).toString()(lib/helpers/toURLEncodedForm.js),支持 formSerializer 自定义 indexes/dots 风格 |
Multipart (multipart/form-data) |
指定该 Content-Type、传入 FormData 或文件列表 | 走 toFormData(data, FormData 实例, formSerializer)(lib/helpers/toFormData.js),文件列表会以 files[] 键包装 |
另外,ArrayBuffer / Buffer / Stream / File / Blob / ReadableStream 会原样透传,ArrayBufferView 取其 .buffer。示例:
// JSON:对象自动序列化
await axios.post('/users', { name: 'Tom', age: 18 });
// URL 编码表单
await axios.post('/login', { user: 'u', pass: 'p' }, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
// Multipart:直接用标准 FormData
const fd = new FormData();
fd.append('file', fileInput.files[0]);
await axios.post('/upload', fd); // Content-Type 与 boundary 由环境自动设置
相关文档:docs/pages/advanced/x-www-form-urlencoded-format.md、docs/pages/advanced/multipart-form-data-format.md;单测:tests/unit/toFormData.test.js。
5.8 以 JSON 形式提交 HTML 表单
默认 transformRequest 的开头专门处理了 HTML 表单元素(lib/defaults/index.js):若 data 是 <form> DOM 元素,先转成 FormData;若此时 Content-Type 是 application/json,则再经 formDataToJSON 转成 JSON 对象后 JSON.stringify 发出:
// 把一个 HTML 表单以 JSON 提交到后端
const form = document.querySelector('#my-form');
await axios.post('/submit', form, {
headers: { 'Content-Type': 'application/json' },
});
库入口还暴露了 axios.formToJSON,它内部就是 formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing),见 lib/axios.js。转换实现与测试:lib/helpers/formDataToJSON.js、tests/unit/helpers/formDataToJSON.test.js;文档:docs/pages/advanced/html-form-processing.md。
5.9 进度捕获(浏览器与 Node.js,含速率与剩余时间)
onUploadProgress / onDownloadProgress 回调在两个环境中都可用,且事件对象携带比原生更丰富的字段:progress、loaded、total,以及计算出的 rate(传输速率,bytes/s)和 estimated(预估剩余秒数)。
await axios.get('/large-file', {
responseType: 'stream',
onDownloadProgress: (e) => {
console.log(
`${e.progress.toFixed(2)}% 速度 ${e.rate} B/s 预计剩余 ${e.estimated}s`
);
},
});
源码层面,事件节流由 lib/helpers/throttle.js 的装饰器完成(默认频率由 throttle 配置控制,防止高频 IO 事件冲刷主线程);速率计算由 lib/helpers/speedometer.js 的滑动窗口采样器完成(默认保留 10 个样本、至少经过 1000ms 才输出速率);事件字段的组装逻辑见 lib/helpers/progressEventReducer.js。Node.js 端由 http 适配器(lib/adapters/http.js)在收到 response/上传 socket 数据时触发。测试:tests/unit/helpers/progressEventReducer.test.js、tests/browser/progress.browser.test.js、Bun/ESM smoke 测试中的 progress 用例;文档:docs/pages/advanced/progress-capturing.md。
5.10 Node.js 带宽限制(rate limiting)
maxRate 配置仅在 Node.js 的 http 适配器中生效,可以设为单一数值(上传下载同速)或 [上传速率, 下载速率] 数组,单位 bytes/s。从源码看,lib/adapters/http.js 会读取 config.maxRate,将其换算后传给 AxiosTransformStream 包装的上传/下载流:
// 上传最多 1MB/s,下载最多 100KB/s
await axios.post('https://api.example.com/upload', bigPayload, {
maxRate: [1024 * 1024, 100 * 1024],
});
限速的具体实现在 lib/helpers/AxiosTransformStream.js:以 maxRate 为依据对 TransformStream 的 chunk 做流量整形。测试与文档:tests/unit/helpers/(流相关用例)、docs/pages/advanced/rate-limiting.md。
5.11 兼容规范 FormData / Blob(含 Node.js)
axios 依赖“符合规范”的 FormData 与 Blob,而不是各环境的私有实现。默认配置在 lib/defaults/index.js 中显式注入了平台类:
env: {
FormData: platform.classes.FormData,
Blob: platform.classes.Blob,
},
platform.classes 按运行环境指向不同来源:浏览器端直接是 window.FormData / window.Blob(lib/platform/browser/classes/),Node.js 端优先使用 Node 18+ 内建实现,否则回退到兼容层(lib/platform/node/classes/FormData.js)。transformRequest 在构造 multipart 请求时也从该 env.FormData 取类(lib/defaults/index.js)。因此无论浏览器还是 Node.js,FormData/Blob 行为是一致的;这也是 formdata-node 出现在 devDependencies(用于旧 Node 版本测试)的原因。
5.12 客户端 XSRF 防护
对于同源请求,axios 会自动读取 cookie 中名为 XSRF-TOKEN 的值,并写入请求头 X-XSRF-TOKEN,两者名称均可配置(默认值见 lib/defaults/index.js):
await axios.post('/transfer', { amount: 100 }, {
xsrfCookieName: 'XSRF-TOKEN', // 默认值
xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
});
读取 cookie 的逻辑在 lib/helpers/cookies.js,在浏览器与 http 适配器中于发送前调用;服务端需保证 CORS 允许暴露相应头(Access-Control-Expose-Headers)。测试:tests/browser/xsrf.browser.test.js;文档:docs/pages/advanced/authentication.md。
六、特性与实现对照速查表
| 特性 | 关键配置/API | 主要实现位置 |
|---|---|---|
| Isomorphic | exports 条件入口、lib/platform/* |
package.json、lib/platform/ |
| Fetch 适配器(可选) | adapter: 'fetch' / ['xhr', 'http', 'fetch'] |
lib/adapters/adapters.js、lib/adapters/fetch.js |
| Promise API | 所有请求方法 | lib/core/Axios.js、lib/core/settle.js |
| 拦截器 | interceptors.request/response |
lib/core/InterceptorManager.js |
| 数据转换 | transformRequest / transformResponse |
lib/defaults/index.js |
| 取消 | AbortController / CancelToken |
lib/cancel/ |
| 超时 | timeout(默认 0 不启用) |
lib/defaults/index.js |
| 嵌套查询参数 | params |
lib/helpers/buildURL.js |
| 请求体序列化 | JSON / urlencoded / multipart | lib/defaults/index.js、lib/helpers/toFormData.js |
| HTML 表单转 JSON | axios.formToJSON |
lib/axios.js、lib/helpers/formDataToJSON.js |
| 进度捕获 | onUploadProgress / onDownloadProgress |
lib/helpers/progressEventReducer.js、lib/helpers/speedometer.js |
| Node.js 限速 | maxRate |
lib/adapters/http.js、lib/helpers/AxiosTransformStream.js |
| 规范 FormData/Blob | env.FormData / env.Blob |
lib/platform/ |
| XSRF 防护 | xsrfCookieName / xsrfHeaderName |
lib/helpers/cookies.js |
七、小结
axios 的特性设计有一条清晰的主线:在保持“一套 Promise 化 API 同时跑在浏览器与 Node.js”的前提下,把环境差异封装到平台层与适配器层——Isomorphic 由条件导出与 lib/platform/ 抽象支撑,传输差异由 xhr / http / fetch 三种适配器抹平,Fetch 作为可插拔的新一代传输通道默认关闭、按需开启,且不与现有代码冲突。在此之上,拦截器、数据转换、取消、超时、进度、限速、XSRF 等能力都以配置项或标准 Web API(AbortController、FormData、Blob)为接口,开发者无需关心底层适配器即可一致地复用这些特性。若需要逐项深入,可继续阅读 docs/pages/advanced/ 下的专题文档,并对照 tests/unit/ 中的同名单测验证行为。
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 StartedRust0623
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