首页
/ axios Fetch 适配器实战指南:从一行配置切换到 env 定制 fetch 环境(Tauri / SvelteKit)

axios Fetch 适配器实战指南:从一行配置切换到 env 定制 fetch 环境(Tauri / SvelteKit)

2026-09-06 20:16:01作者:董宙帆

本文基于 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.jsgetAdapter 中:按列表顺序逐一尝试,名字适配器通过 knownAdapters 映射表解析,其中 fetch 是特殊的惰性形态 { get: fetchAdapter.getFetch },只在真正需要时才执行工厂逻辑。

若要显式让 fetch 成为默认适配器,在创建实例时把 adapter 选项设为 'fetch'

import axios from 'axios';

const instance = axios.create({
  adapter: 'fetch',
});

功能对等性与额外能力

官方文档明确:fetch 适配器支持 xhr 适配器的同等功能,包括上传与下载进度捕获,并额外支持 streamformdata 等响应类型(取决于环境是否支持)。

lib/adapters/fetch.js 可以看到,响应类型解析器(resolvers)覆盖 textarrayBufferblobformDatastream 五类:

  • stream 类型仅在 ResponseReadableStream 均受支持时可用,直接返回 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 会剥离 bodyheadersmethodsignalduplexcredentials 这些"由 axios 掌管"的键,避免用户误覆盖内部已解析的行为。
  • maxRedirects: 0:映射为原生 redirect: 'manual',即不跟随重定向。
  • User-Agent:若未设置,fetch 适配器默认写入 axios/<版本号>(Node 原生 fetch 默认 UA 是 node)。
  • Request 构造器可用时,axios 还会补上一组安全默认值(lib/adapters/fetch.jsDEFAULT_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 中有完整实现,几个值得注意的细节:

  1. 预判maybeWithAuthCredentials 先检查 URL 中是否可能出现凭据(含 @:),避免对普通 URL 做 new URL() 解析的开销;
  2. 解码decodeURIComponentSafe 处理 Node 的 WHATWG URL 解析器返回的百分号编码用户名/密码——例如 my%40email.com:pass 会被还原为 my@email.com:pass;编码畸形时回退原值而不会抛错;
  3. 剥离:无论凭据最终取自哪里,URL 中的 username/password 都会被清空后重新生成 url,防止凭据随 URL 泄漏到下游;
  4. 生成:最终统一写入 Authorization: Basic <base64>,其中 base64 编码采用 encodeURIComponent + 逐段转 Latin-1 的现代写法替代了已废弃的 unescape(encodeURIComponent(str)) 模式,对非 ASCII 用户名/密码也安全。

自定义 fetch 环境(v1.12.0+):env 配置

v1.12.0 起,你可以让 fetch 适配器不再使用环境全局的 fetch,而是注入自定义的 fetch 函数,以及配套的 RequestResponse 构造器——全部通过 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;
};

语义约定:缺省、nullundefined

官方文档给出的约定值得逐条记住:

  • 只传自定义 fetch 时,可省略 Request/Response,此时使用全局构造器(lib/adapters/fetch.jsfactory 先以 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 } 时回落到全局 fetchenv: { 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 使用它,并同时禁用全局 RequestResponse

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:构造带 ReadableStream body、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)与 cancelTokenlib/helpers/composeSignals.js 合成单一 AbortSignal,timeout 触发时以 AxiosError(ETIMEDOUT) 作为 abort reason,从而在 catch 分支中能精确区分"超时"与"手动取消",并按原生 Errorcause 语义(不可枚举)挂载原始错误,避免日志工具递归进入 fetch 内部结构。

5. 错误归一化。 catch 分支按优先级处理:已合成的取消/超时错误直接透出;流式 body 阶段的 maxBodyLength 违例按身份匹配抛出;同步检查抛出的 AxiosError 原样再抛(补充 request 引用);TypeError(含 Load failed|fetch)归一为 Network ErrorERR_NETWORK);其余经 AxiosError.from 包装。

6. 最终结算。 响应数据经 resolvers[responseType] 取出后,交给 lib/core/settle.jssettlevalidateStatus 决定 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.jslib/adapters/adapters.jslib/defaults/index.jstests/unit/adapters/fetch.test.js 中逐条对照验证。

登录后查看全文
热门项目推荐
相关项目推荐