Axios 适配器机制详解:内置适配器选择、getAdapter 优先级解析与自定义适配器实战
Axios 的适配器(Adapter)是 HTTP 请求真正发出的执行层:配置合并、请求拦截器、请求转换完成后,全部交由适配器落地。本文基于 axios 官方文档与仓库源码,系统讲解 xhr / http / fetch 三个内置适配器的环境选择机制(源码中实际优先级为 ['xhr', 'http', 'fetch'])、getAdapter 的解析与报错逻辑,以及编写自定义适配器的完整契约(函数签名、响应对象结构、settle 收尾、validateStatus 自定义判定)和 TypeScript 泛型适配器的类型保留写法。读完后你能够:在浏览器/Node.js/边缘环境间按需切换适配器,并为测试桩、Mock 传输或非标准运行时编写符合 axios 内部规范的自定义适配器。
内置适配器与默认优先级
axios 默认按一个有序优先级列表选择适配器。该默认值直接写在默认配置中:
// 源码位置:lib/defaults/index.js 第 41 行
adapter: ['xhr', 'http', 'fetch'],
即默认顺序是 xhr → http → fetch。运行时逐个探测,第一个被当前环境支持(且在当前构建产物中可用)的适配器生效:
xhr:基于XMLHttpRequest,浏览器默认路径(实现见 lib/adapters/xhr.js);http:基于 Node.js 的http/https模块,Node 环境默认路径(实现见 lib/adapters/http.js);fetch:用于两者都不可用的环境,如 Cloudflare Workers、Deno、Bun 等(实现见 lib/adapters/fetch.js)。
三个适配器统一注册在"已知适配器映射表"中(lib/adapters/adapters.js):
const knownAdapters = {
http: httpAdapter,
xhr: xhrAdapter,
fetch: {
get: fetchAdapter.getFetch,
},
};
注意一个细节:fetch 注册的并不是直接可调用的函数,而是一个带 get 方法的对象。从源码结构看,这是惰性解析——getFetch(config) 只有在请求真正发起、且当前环境探测到 fetch API 可用时才返回真实适配器;探测失败则返回 false,让解析流程继续尝试列表中的下一个适配器。此外源码为每个适配器函数用 Object.defineProperty 打上 name 与 adapterName 属性(lib/adapters/adapters.js),主要用于调试时在错误信息中识别出具体是哪一个适配器。
按名称选择内置适配器
通过配置项 adapter 传入字符串名称即可显式指定某个内置适配器:
// 使用 fetch 适配器
const instance = axios.create({ adapter: "fetch" });
// 使用 XHR 适配器(浏览器中的默认)
const instance = axios.create({ adapter: "xhr" });
// 使用 HTTP 适配器(Node.js 中的默认)
const instance = axios.create({ adapter: "http" });
传入适配器名称数组
adapter 也接受名称数组,axios 按数组顺序取第一个当前环境支持的:
const instance = axios.create({ adapter: ["fetch", "xhr", "http"] });
数组解析的完整逻辑在 getAdapter 函数中(lib/adapters/adapters.js):
- 非数组输入先被规范化为数组,逐项遍历;
- 每一项若已是函数(或
null/false),视为已解析的句柄直接使用;否则按名称(转小写)在knownAdapters中查找,查不到直接抛出AxiosError: Unknown adapter 'xxx'; - 名称对应的注册项若为带
get的对象(即 fetch 适配器),则调用adapter.get(config)做环境探测;探测通过(返回函数)则立即break选中; - 被跳过的适配器会被记入
rejectedReasons,若整个列表遍历完都没有可用适配器,最终抛出带详细原因的AxiosError(错误码ERR_NOT_SUPPORT),例如:
There is no suitable adapter to dispatch the request since :
- adapter xhr is not supported by the environment
- adapter http is not available in the build
其中 is not supported by the environment 表示环境不支持,is not available in the build 表示当前构建产物未包含该适配器。这套区分对"为什么我的浏览器构建里选不到 http"这类问题排查非常有用。
关于 fetch 适配器的更多细节(如进度事件、responseType 处理等),可参考文档中的 Adaptateur Fetch 页面。
请求管线中适配器所处的位置
理解自定义适配器契约,先要看清适配器的上游与下游。适配器的调用发生在 lib/core/dispatchRequest.js:
// lib/core/dispatchRequest.js(节选)
export default function dispatchRequest(_config) {
// 取消检查
throwIfCancellationRequested(config);
config.headers = AxiosHeaders.from(utils.getSafeProp(config, 'headers'));
// 执行请求转换器 transformRequest
config.data = transformData.call(config, config.transformRequest);
// ...
// 解析并调用适配器
const adapter = adapters.getAdapter(config.adapter || defaults.adapter, config);
return adapter(config).then(
function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// 执行响应转换器 transformResponse
response.data = transformData.call(config, config.transformResponse, response);
response.headers = AxiosHeaders.from(response.headers);
return response;
},
function onAdapterRejection(reason) {
// 错误分支:若错误携带 response,同样会执行 transformResponse
if (reason && reason.response) {
reason.response.data = transformData.call(
config, config.transformResponse, reason.response
);
reason.response.headers = AxiosHeaders.from(reason.response.headers);
}
return Promise.reject(reason);
}
);
}
由此可以确认适配器的精确契约(与官方文档中的注释一致):
- 进入适配器之前:配置已与默认值合并(
mergeConfig)、请求拦截器已执行(Axios.js中拦截器链在dispatchRequest之前完成)、transformRequest已执行(config.data已是最终要发送的载荷); - 适配器的职责:真正执行网络请求(或完全自定义的传输逻辑),返回一个以"合法 axios 响应对象"resolve 的 Promise,或以
AxiosError之类的错误 reject; - 离开适配器之后:
transformResponse与响应拦截器才执行。
这也意味着:适配器不应该自己做 JSON 解析之类的响应转换——那是 transformResponse 的职责;但适配器必须保证 reject 时若带有响应体,应以 error.response 形式携带(见下方 settle 行为)。
编写自定义适配器
自定义适配器就是一个接受 config、返回 Promise 的函数。官方文档给出的完整示例(以原生 fetch 为传输起点,适合直接改造为任意传输层):
import axios from "axios";
import { settle } from "axios/unsafe/core/settle.js";
function myAdapter(config) {
/**
* 到达这里时:
* - 配置已合并默认值
* - transformRequest 已执行
* - 请求拦截器已执行
*
* 适配器现在负责执行请求并返回合法响应对象。
*/
return new Promise((resolve, reject) => {
// 在此编写自定义请求逻辑。
// 此示例使用原生 fetch API 作为起点。
fetch(config.url, {
method: config.method?.toUpperCase() ?? "GET",
headers: config.headers?.toJSON() ?? {},
body: config.data,
signal: config.signal,
})
.then(async (fetchResponse) => {
const responseData = await fetchResponse.text();
const response = {
data: responseData,
status: fetchResponse.status,
statusText: fetchResponse.statusText,
headers: Object.fromEntries(fetchResponse.headers.entries()),
config,
request: null,
};
// settle 根据 HTTP 状态码 resolve 或 reject 该 Promise
settle(resolve, reject, response);
/**
* 在此之后:
* - transformResponse 将执行
* - 响应拦截器将执行
*/
})
.catch(reject);
});
}
const instance = axios.create({ adapter: myAdapter });
响应对象必须包含的字段
从示例与源码两端交叉印证,自定义适配器 resolve 的对象至少需要:
| 字段 | 说明 |
|---|---|
data |
原始响应体(此处取 text;transformResponse 会在其后按需解析 JSON) |
status / statusText |
HTTP 状态码与状态文本 |
headers |
响应头(普通对象即可,dispatchRequest 会将其包成 AxiosHeaders) |
config |
回传当前请求配置(settle 依赖它读取 validateStatus) |
request |
底层请求句柄;无对应对象时置 null 即可 |
settle:按 validateStatus 决定成败
示例中的 settle 来自 lib/core/settle.js,其实现只有十几行,但正是 axios "什么状态码算失败"的裁决点:
// lib/core/settle.js
export default function settle(resolve, reject, response) {
const validateStatus = response.config.validateStatus;
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
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
));
}
}
三个要点:
- 默认行为:
validateStatus的默认值是"2xx 通过",因此settle对 2xx resolve、对其余状态码 reject——这与内置适配器的行为一致,官方文档也明确提示:想要非默认的成功状态区间,应使用配置项validateStatus定制,而不是在适配器里手判状态码; - 错误码自动区分:4xx 生成
ERR_BAD_REQUEST,其余(如 5xx)生成ERR_BAD_RESPONSE,配合isAxiosError(err) && err.response的判定模式可以直接用于错误处理; - reject 时响应体不丢失:
AxiosError构造时传入了response,且 dispatchRequest.js 的拒绝分支也会对error.response执行transformResponse,所以在catch分支中同样能拿到已转换(如 JSON 解析后)的错误响应体。
另外,config.signal(AbortSignal)应像示例那样透传给底层传输,这样 CancelToken/AbortController 取消机制在自定义适配器中依然生效(取消检查见 lib/core/dispatchRequest.js 的 throwIfCancellationRequested)。
TypeScript:用泛型保留请求体与查询参数类型
对于 TypeScript 项目,官方文档给出了利用 axios 类型系统让适配器携带完整类型信息的写法。请求侧的 body 与 params 类型可以写进 InternalAxiosRequestConfig<T, P>,响应侧写进 AxiosPromise<D, T, P>:
import type {
AxiosPromise,
InternalAxiosRequestConfig,
} from "axios";
interface RequestBody {
includeArchived: boolean;
}
interface SearchParams {
query: string;
}
interface SearchResponse {
results: string[];
}
const searchAdapter = (
config: InternalAxiosRequestConfig<RequestBody, SearchParams>
): AxiosPromise<SearchResponse, RequestBody, SearchParams> =>
Promise.resolve({
data: { results: [] },
status: 200,
statusText: "OK",
headers: {},
config,
});
这种写法使得 instance.get<SearchResponse, void, SearchResponse, RequestBody, SearchParams>(...) 之类的调用链在适配器层面也能维持端到端的类型推导,而不是在适配器边界退化为 any。
与内置适配器行为的对照
从仓库源码结构看,内置适配器也严格遵循同一契约,可供自定义适配器参照:
- lib/adapters/fetch.js:
factory(env)工厂函数按环境探测 fetch 可用性并返回适配器,内部同样调用settle(见其 import settle from '../core/settle.js'); - lib/adapters/http.js 与 lib/adapters/xhr.js:分别基于 Node
http模块与XMLHttpRequest实现,response.request字段分别挂接底层请求对象; - 各适配器的行为由单元测试覆盖,如 tests/unit/adapters/fetch.test.js、tests/unit/adapters/xhr.test.js、tests/unit/adapters/http.test.js,以及 tests/unit/adapters/adapters.test.js 对
getAdapter选择与报错逻辑的直接测试。编写自定义适配器时,这些用例是验证"你的响应对象是否满足契约"的良好参照。
小结
- axios 默认按
['xhr', 'http', 'fetch']的优先级探测适配器(lib/defaults/index.js),getAdapter支持名称、数组与函数三种输入,并对"环境不支持/构建未包含"给出可区分的报错(lib/adapters/adapters.js); - 自定义适配器的契约是:接收合并后的
config,返回以合法响应对象(data/status/statusText/headers/config/request)resolve 的 Promise;请求转换与请求拦截器在其之前、响应转换与响应拦截器在其之后(lib/core/dispatchRequest.js); - 用
settle收尾可获得与内置适配器一致的 2xx 判定与ERR_BAD_REQUEST/ERR_BAD_RESPONSE错误码(lib/core/settle.js),需要自定义成功状态区间时改用validateStatus配置项。
这条机制让 axios 在浏览器、Node.js 与边缘运行时间无缝切换传输层,也为测试打桩和自定义传输协议留出了标准化的扩展点。
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