axios Fetch 适配器实战指南:从一行配置切换到 env 定制 fetch 环境(Tauri / SvelteKit)
本文基于 axios 官方文档 fetch-adapter.md 展开,系统讲解 fetch 适配器的启用方式、功能边界(进度监听、stream/formdata 响应类型、URL 凭据解析)以及 v1.12.0 起通过 env 配置注入自定义 fetch/Request/Response 的能力,并结合 lib/adapters/fetch.js 源码剖析其能力探测、尺寸限制与错误归因的实现细节。读完后,你应能正确在不同运行环境(Node、浏览器、Tauri、SvelteKit 服务端)下选型并定制 fetch 适配器,理解其底层调用链与限制条件。
定位:fetch 适配器是什么,为什么有它
fetch 适配器是 axios 自 1.7.0 版本引入的新一代请求适配器。它让 axios 基于浏览器/运行时原生的 fetch API 发起请求,从而在跨平台场景下获得"两全其美"的效果:既保留 axios 完整的拦截器、进度、转换等能力,又复用运行时的网络栈(含 HTTP/2、连接复用等运行时自带特性)。
它的默认参与方式是"兜底"角色。axios 的默认适配器列表是:
// lib/defaults/index.js
const defaults = {
adapter: ['xhr', 'http', 'fetch'],
// ...
};
也就是说,当 xhr(浏览器)和 http(Node.js)适配器在构建中不可用、或当前环境不支持时,fetch 会被自动选中。从源码结构看,适配器解析发生在 lib/adapters/adapters.js 的 getAdapter 中:按列表顺序逐一尝试,名字适配器通过 knownAdapters 映射表解析,其中 fetch 是特殊的惰性形态 { get: fetchAdapter.getFetch },只在真正需要时才执行工厂逻辑。
若要显式让 fetch 成为默认适配器,在创建实例时把 adapter 选项设为 'fetch':
import axios from 'axios';
const instance = axios.create({
adapter: 'fetch',
});
功能对等性与额外能力
官方文档明确:fetch 适配器支持 xhr 适配器的同等功能,包括上传与下载进度捕获,并额外支持 stream、formdata 等响应类型(取决于环境是否支持)。
从 lib/adapters/fetch.js 可以看到,响应类型解析器(resolvers)覆盖 text、arrayBuffer、blob、formData、stream 五类:
stream类型仅在Response与ReadableStream均受支持时可用,直接返回res.body;- 其余类型直接调用
Response对应方法(如res.text()),若运行时的Response不支持某个方法,会抛出ERR_NOT_SUPPORT类型的AxiosError。
进度捕获的实现依赖 lib/helpers/trackStream.js 中的 trackStream:它把响应体(或请求体)包装成一个新的 ReadableStream,按固定分块(fetch 适配器中 DEFAULT_CHUNK_SIZE = 64 * 1024,见 lib/adapters/fetch.js)统计已读字节,再交给 progressEventDecorator / progressEventReducer 转换为 axios 统一的进度事件。上传进度则走 duplex: 'half' 的 Request 流式 body 路径——这条路径仅在能力探测 supportsRequestStream 通过时启用。
其他与 xhr 适配器语义对齐的关键配置:
withCredentials:默认'same-origin',布尔值会映射为'include'/'omit';若运行时的Request.prototype不含credentials属性(如某些 Cloudflare Workers 环境会直接抛错),axios 会直接不下发该选项。fetchOptions:允许透传任意原生 fetch 选项,但 axios 会剥离body、headers、method、signal、duplex、credentials这些"由 axios 掌管"的键,避免用户误覆盖内部已解析的行为。maxRedirects: 0:映射为原生redirect: 'manual',即不跟随重定向。User-Agent:若未设置,fetch 适配器默认写入axios/<版本号>(Node 原生 fetch 默认 UA 是node)。- 当
Request构造器可用时,axios 还会补上一组安全默认值(lib/adapters/fetch.js 的DEFAULT_REQUEST_OPTIONS):cache: 'default'、redirect: 'follow'、mode: 'cors'、keepalive: false等,且这些默认值只填充用户未显式提供的键。
Basic 认证:从 URL 中读取凭据
当未显式提供 auth 配置时,fetch 适配器支持直接从请求 URL 读取 HTTP Basic 凭据,例如 https://user:pass@example.com。URL 中的百分号编码凭据会在生成 Authorization 头之前被解码,且显式 auth 始终优先于 URL 内嵌凭据。
这段行为在 lib/adapters/fetch.js 中有完整实现,几个值得注意的细节:
- 预判:
maybeWithAuthCredentials先检查 URL 中是否可能出现凭据(含@或:),避免对普通 URL 做new URL()解析的开销; - 解码:
decodeURIComponentSafe处理 Node 的 WHATWG URL 解析器返回的百分号编码用户名/密码——例如my%40email.com:pass会被还原为my@email.com:pass;编码畸形时回退原值而不会抛错; - 剥离:无论凭据最终取自哪里,URL 中的
username/password都会被清空后重新生成url,防止凭据随 URL 泄漏到下游; - 生成:最终统一写入
Authorization: Basic <base64>,其中 base64 编码采用encodeURIComponent+ 逐段转 Latin-1 的现代写法替代了已废弃的unescape(encodeURIComponent(str))模式,对非 ASCII 用户名/密码也安全。
自定义 fetch 环境(v1.12.0+):env 配置
从 v1.12.0 起,你可以让 fetch 适配器不再使用环境全局的 fetch,而是注入自定义的 fetch 函数,以及配套的 Request、Response 构造器——全部通过 env 配置项完成。这在自定义运行环境、或使用自带 fetch 实现的应用框架(如 Tauri、SvelteKit)时非常有用。
env 的类型定义见 index.d.ts:
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;
};
语义约定:缺省、null 与 undefined
官方文档给出的约定值得逐条记住:
- 只传自定义
fetch时,可省略Request/Response,此时使用全局构造器(lib/adapters/fetch.js 中factory先以skipUndefined策略把全局Request/Response合并进env,用户值覆盖全局值); - 如果你的自定义
fetch与全局Request/Response不兼容,应显式传null禁用对应构造器; - 注意:
Request/Response设为null后,fetch 适配器将无法捕获上传与下载进度(因为进度依赖对Request.body/Response.body流的包装)。
测试用例 tests/unit/adapters/fetch.test.js 精确验证了这些边界:env: { fetch: undefined } 时回落到全局 fetch;env: { Request: null } 时请求以 (url, init) 形式直发(且 init 是 null-prototype 对象,免疫 Object.prototype 污染);env: { Response: null } 时响应按鸭子类型(text()/headers 等)消费。
基本示例
import customFetchFunction from 'customFetchModule';
const instance = axios.create({
adapter: 'fetch',
onDownloadProgress(e) {
console.log('downloadProgress', e);
},
env: {
fetch: customFetchFunction,
Request: null, // null -> 禁用全局 Request 构造器
Response: null, // 禁用全局 Response 构造器
},
});
在 Tauri 中使用
Tauri 提供了平台级 fetch,原生层发起请求可绕过浏览器 CORS 限制。在 Tauri 应用中使用 axios 的最小配置如下:
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');
在 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 };
}
源码剖析:factory、能力探测与错误归因
结合 lib/adapters/fetch.js 的实现,可以梳理出 fetch 适配器的完整执行链路:
1. 工厂与实例缓存。 getFetch(config)(L671-L692)以 { fetch, Request, Response } 三元组为键维护一个 seedCache(嵌套 Map),相同的 env 组合复用同一个工厂实例,避免重复执行昂贵的能力探测。
2. 能力探测。 factory(env) 内部构造性测试两项关键能力:
supportsRequestStream:构造带ReadableStreambody、method: 'POST'、duplex: 'half'的Request,验证运行时的流式上传支持;supportsResponseStream:验证new Response('').body是否为ReadableStream,决定responseType: 'stream'、下载进度、流式maxContentLength检查是否可用。
3. 尺寸限制的多层防线。 maxContentLength / maxBodyLength 在 fetch 适配器里不是简单的一次检查,而是分层执行:
data:URL:先估算解码后字节数(estimateDataURLDecodedBytes),超限直接拒绝,避免物化超大负载;- 已知大小的请求体:用
getBodyLength计算实际尺寸(刻意不信任调用方声明的Content-Length),超限抛ERR_BAD_REQUEST; - 流式请求体:经
trackStream逐块计数,超限抛错并挂到pendingBodyError; - 响应体:先对服务端声明的
content-length做廉价预检,再在流式读取中逐块累计;无ReadableStream支持的旧运行时则在text/arrayBuffer/blob物化后按byteLength/size兜底检查。
4. 取消与超时的统一。 signal(AbortSignal)与 cancelToken 经 lib/helpers/composeSignals.js 合成单一 AbortSignal,timeout 触发时以 AxiosError(ETIMEDOUT) 作为 abort reason,从而在 catch 分支中能精确区分"超时"与"手动取消",并按原生 Error 的 cause 语义(不可枚举)挂载原始错误,避免日志工具递归进入 fetch 内部结构。
5. 错误归一化。 catch 分支按优先级处理:已合成的取消/超时错误直接透出;流式 body 阶段的 maxBodyLength 违例按身份匹配抛出;同步检查抛出的 AxiosError 原样再抛(补充 request 引用);TypeError(含 Load failed|fetch)归一为 Network Error(ERR_NETWORK);其余经 AxiosError.from 包装。
6. 最终结算。 响应数据经 resolvers[responseType] 取出后,交给 lib/core/settle.js 的 settle 按 validateStatus 决定 resolve 或 reject,与 xhr/http 适配器共享同一套结算逻辑。
选型小结
- 默认场景下无需干预:
adapter: ['xhr', 'http', 'fetch']的解析顺序已让 fetch 成为现代环境的天然兜底; - 需要确定性地走原生 fetch(例如统一行为、利用运行时 HTTP/2 栈)时,实例级设置
adapter: 'fetch'; - 在 Tauri、SvelteKit 等自带
fetch语义的环境,用env注入自定义实现,并在与全局构造器不兼容时传null禁用Request/Response——代价是失去进度监听,这一点在 tests/unit/adapters/fetch.test.js 中也有对应的无Response路径回归测试; - 关注
maxContentLength/maxBodyLength语义时注意:fetch 适配器对调用方声明的Content-Length保持"不信任",始终以实际读取的字节数为准。
以上结论均可在 lib/adapters/fetch.js、lib/adapters/adapters.js、lib/defaults/index.js 与 tests/unit/adapters/fetch.test.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