Axios fetch 适配器实战:启用方式、进度捕获与自定义 fetch 环境(Tauri / SvelteKit)
Axios 从 1.7.0 版本起引入了 fetch 适配器,让你在保留 Axios 完整 API 的同时,用现代 fetch 底层完成请求。本文基于仓库中文档 fetch-adapter.md 与源码实现,系统讲解:如何显式启用 fetch 适配器、它在 xhr/http 之外的能力(进度捕获、stream/formdata 响应类型、URL 内嵌 Basic 认证),以及 v1.12.0 起通过 env 配置注入自定义 fetch/Request/Response 的完整方案(含 Tauri、SvelteKit 两个真实场景)。读完你能掌握 fetch 适配器的适配逻辑、能力边界与生产环境的接线方式。
一、fetch 适配器是什么,何时会被用到
fetch 适配器是 Axios 1.7.0 引入的新适配器,让你以 fetch API 作为传输层使用 Axios,"兼得两者之长"(Promise 化的 Axios 配置体系 + 现代 fetch 运行时)。
默认的适配器选择顺序
Axios 的默认 adapter 配置是一个按优先级尝试的列表,在 lib/defaults/index.js 中定义:
adapter: ['xhr', 'http', 'fetch'],
解析逻辑位于 lib/adapters/adapters.js 的 getAdapter:按顺序遍历列表,对每个名字去 knownAdapters 表中查找,取第一个"可用"的适配器;全部失败时抛出 AxiosError.ERR_NOT_SUPPORT,错误信息会区分"该适配器不在 build 中"(is not available in the build)与"环境不支持"(is not supported by the environment)。其中 fetch 在表中是一个带 getter 的特殊条目(adapters.js L16-L22):
const knownAdapters = {
http: httpAdapter,
xhr: xhrAdapter,
fetch: {
get: fetchAdapter.getFetch, // 惰性解析,见下文
},
};
也就是说:只有当 xhr 与 http 都不在你的 build 中、或当前环境不支持时,fetch 才会作为兜底被自动选中。想强制默认走 fetch,必须显式把 adapter 设为 'fetch':
import axios from 'axios';
const instance = axios.create({
adapter: 'fetch',
});
能力对齐:与 xhr 适配器相同的功能集
文档明确 fetch 适配器支持与 xhr 适配器相同的功能,特别是上传/下载进度捕获,并额外支持 stream、formdata 等响应类型(取决于环境)。这一点在源码 lib/adapters/fetch.js 中有完整对应:
- 响应类型解析器(fetch.js L145-L167):注册了
text、arrayBuffer、blob、formData、stream五种 resolver。其中stream仅在环境支持Response.body为ReadableStream时可用(supportsResponseStream检测,L140-L143),否则会回落到抛出Response type 'stream' is not supported的AxiosError.ERR_NOT_SUPPORT。 - 上传进度(fetch.js L351-L413):当环境同时具备
Request构造器、ReadableStream且通过duplex特性探测(supportsRequestStream)时,请求体会被包成可追踪的流,逐块(默认块大小DEFAULT_CHUNK_SIZE = 64 * 1024,L18)回调onUploadProgress。 - 下载进度(fetch.js L508-L555):当需要
onDownloadProgress(或需要强制maxContentLength)时,response.body流被trackStream包裹,按块累计字节数后回调进度事件。
这也解释了后文一个重要限制:进度捕获依赖
Request/Response构造器。若你在env中把它们设为null,supportsRequestStream与supportsResponseStream都会为 false,上传/下载进度就无法捕获。
此外源码中还包含一批值得了解的默认行为:
| 行为 | 源码位置 | 说明 |
|---|---|---|
| 默认 fetch 选项 | fetch.js L20-L30 | cache: 'default'、redirect: 'follow'、mode: 'cors' 等,仅当调用方未指定时生效 |
| 取消与超时信号合成 | fetch.js L232-L235 | composeSignals([signal, cancelToken.toAbortSignal()], timeout),三者统一为一个 AbortSignal |
maxRedirects: 0 |
fetch.js L478-L484 | 对应 redirect: 'manual',不再自动跟随重定向 |
withCredentials |
fetch.js L217 | 默认 'same-origin',布尔值会映射为 'include'/'omit';Cloudflare Workers 等不支持 credentials 的环境会自动跳过(L421) |
| User-Agent | fetch.js L437 | 未设置时写入 axios/<版本>,避免 Node 下 fetch 默认的 node 标识 |
| 请求头安全清洗 | fetch.js L456 | 通过 toByteStringHeaderObject 规范化,剥离 CRLF 注入字符 |
二、从 URL 读取 HTTP Basic 认证凭据
一个容易忽略的能力:当 auth 配置被省略时,fetch 适配器可以直接从请求 URL 中读取 Basic 认证凭据,例如 https://user:pass@example.com。URL 中百分号编码的凭据(如 my%40email.com)会先解码,再用于生成 Authorization 头;而显式配置的 auth 始终优先于 URL 内嵌凭据。
源码对应 lib/adapters/fetch.js L276-L301 的三段逻辑:
maybeWithAuthCredentials(url)预检 URL 在协议之后是否包含@或:(L71-L78);- 命中后解析 URL,若未配置
auth,则取parsedURL.username/password并经decodeURIComponentSafe解码(非法编码时回退原值、不抛错,L51-L61);随后会把 URL 中的用户名/密码清空,避免凭据随 URL 泄漏到日志与重定向链; - 最终删除旧
authorization头并写入Authorization: Basic <btoa(user:pass)>。编码使用encodeUTF8把 UTF-8 转为 Latin-1 字节串再btoa(L42-L45),这是对已废弃的unescape(encodeURIComponent(str))写法的现代替代,保证含非 ASCII 字符的密码也能正确编码。
三、自定义 fetch(v1.12.0+)
从 v1.12.0 开始,你可以通过 env 配置项向 fetch 适配器注入自定义的 fetch 函数以及配套的 Request、Response 构造器,替代环境全局实现。这对运行在自定义运行时或自带 fetch 实现的应用框架(Tauri、SvelteKit、Cloudflare Workers 等)中的 Axios 非常关键。
env 的类型定义见 index.d.ts L443-L451:
env?: {
FormData?: new (...args: any[]) => object;
fetch?: (input: URL | Request | string, init?: RequestInit) => Promise<Response>;
Request?: new (input: URL | Request | string, init?: RequestInit) => Request;
Response?: new (
body?: ArrayBuffer | ArrayBufferView | Blob | FormData | URLSearchParams | string | null,
init?: ResponseInit
) => Response;
};
注意两点实现细节:
- 默认值合并:适配器工厂
factory(env)会把传入的env与全局globalThis.Request/Response做skipUndefined合并(fetch.js L80-L99)。因此省略Request/Response时会用全局构造器;而你的自定义fetch若与全局构造器不兼容,应显式传null关闭它们。 - 按环境组合缓存:
getFetch(config)以Request、Response、fetch三元组为键在seedCache(Map)中缓存适配器工厂结果(fetch.js L669-L692),同一套环境组合只构建一次;这也正是knownAdapters.fetch使用 getter 惰性解析的原因。 - 进度捕获的代价:
Request/Response设为null后,上传/下载进度捕获不可用(与第一节的能力矩阵一致)。
基本示例
import customFetchFunction from 'customFetchModule';
const instance = axios.create({
adapter: 'fetch',
onDownloadProgress(e) {
console.log('downloadProgress', e);
},
env: {
fetch: customFetchFunction,
Request: null, // null -> 禁用该构造器
Response: null,
},
});
与 Tauri 配合使用
Tauri 提供了平台级 fetch 函数,来自原生层发起的请求可以绕过浏览器的 CORS 限制。下面是 Tauri 应用中接线的最小配置:
import { fetch } from '@tauri-apps/plugin-http';
import axios from 'axios';
const instance = axios.create({
adapter: 'fetch',
onDownloadProgress(e) {
console.log('downloadProgress', e);
},
env: {
fetch,
},
});
const { data } = await instance.get('https://google.com');
这里只注入 fetch 而未禁用 Request/Response,因此仍可捕获下载进度。
与 SvelteKit 配合使用
SvelteKit 为服务端 load 函数提供了一个自定义 fetch 实现,负责 Cookie 透传与相对 URL 处理。由于它的 fetch 与标准 URL API 不兼容,必须显式让 Axios 使用它,并且同时禁用全局 Request 与 Response 构造器:
export async function load({ fetch }) {
const { data: post } = await axios.get('https://jsonplaceholder.typicode.com/posts/1', {
adapter: 'fetch',
env: {
fetch,
Request: null,
Response: null,
},
});
return { post };
}
这也是"传 null 禁用构造器"用法的直接推论:代价是失去进度事件,换来与宿主运行时 100% 兼容。
四、请求体与响应体的大小守卫
fetch 适配器对 maxContentLength / maxBodyLength 的执行比"简单比较"细致得多,理解这些有助于排查超限错误:
maxContentLength:先做廉价的预检查——若服务端Content-Length已超上限,直接拒绝(fetch.js L496-L506);对流式响应则在trackStream回调中逐块累计、超限即抛ERR_BAD_RESPONSE;在不支持ReadableStream的旧运行时,回退为对物化结果按byteLength/size/字符串字节数校验(L567-L589)。对data:URL 还会先估算解码后字节数,避免物化超大载荷(L306-L316)。maxBodyLength:发送前对可确定尺寸的请求体取实际尺寸校验,刻意不信任调用方声明的Content-Length(防止低报绕过,L318-L331);流式请求体则在 fetch 消费时逐块计数(L338-L349)。
错误归因上,fetch 适配器还处理了若干运行时差异:Safari 的 abort 可能抛出 getter 会抛异常的 DOMException 类对象,源码优先读取合成 signal 的 reason 以保留"超时 vs 取消"语义(L609-L625);TypeError 且消息匹配 /Load failed|fetch/ 时映射为 AxiosError.ERR_NETWORK("Network Error"),并把原始错误挂到不可枚举的 cause 上(L644-L662)。
五、测试验证与延伸阅读
仓库中 tests/unit/adapters/fetch.test.js 是 fetch 适配器的主测试套件(1800+ 行,基于本地 HTTP 服务),覆盖:
- 非法 URL 在 fetch 归一化前被拒绝并保留
config(ERR_INVALID_URL); - 请求头 CRLF 字符清洗(防止头注入);
- 不继承
Symbol.iterator污染的头构造; - 以及进度、认证、流式响应、信号等核心路径。
如果你还想对比三种适配器的分工,可参考文档 adapters.md;fetch 适配器的原始文档见 docs/pages/advanced/fetch-adapter.md。
六、速查清单
| 场景 | 配置要点 |
|---|---|
| 强制使用 fetch 底层 | axios.create({ adapter: 'fetch' }) |
| 依赖自动兜底 | 无需配置;xhr/http 不可用时自动落到 fetch |
| 注入框架自带 fetch(如 Tauri) | env: { fetch },保留全局构造器以保留进度能力 |
| 注入不兼容全局 URL API 的 fetch(如 SvelteKit load) | env: { fetch, Request: null, Response: null },放弃进度事件 |
| URL 内嵌 Basic 凭据 | 直接写 https://user:pass@example.com,auth 配置优先级更高 |
| 精确控制重定向 | maxRedirects: 0 等价于 redirect: 'manual' |
适用前提与限制:以上结论均基于当前仓库源码(lib/adapters/fetch.js、lib/adapters/adapters.js、lib/defaults/index.js),对应能力要求环境提供全局 fetch;stream/formdata 响应类型与进度捕获分别依赖 ReadableStream 与 Request/Response 构造器的可用性,注入自定义环境时请按第三节的能力矩阵自行权衡。
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