首页
/ Axios Fetch 适配器实战指南:启用方式、URL 凭据处理与自定义 fetch 注入

Axios Fetch 适配器实战指南:启用方式、URL 凭据处理与自定义 fetch 注入

2026-09-05 20:55:54作者:尤峻淳Whitney

Axios 从 1.7.0 版本引入了 fetch 适配器,它让 axios 能够基于 Web 标准 fetch API 发起请求,同时保留 axios 一贯的进度捕获、拦截器与错误处理体验。本文基于官方文档 fetch-adapter 的完整内容,结合 lib/adapters/fetch.js 的源码实现,讲清三件事:如何在任意环境中显式启用 fetch 适配器、它如何从请求 URL 中安全地解析 Basic 认证凭据、以及 v1.12.0 之后如何通过 env 选项注入自定义 fetch/Request/Response(Tauri、SvelteKit 等真实场景)。

1. fetch 适配器是什么,何时会被使用

fetch 适配器是 axios 内置的三种请求通道之一。它与 xhr(浏览器 XHR)、http(Node.js 核心模块)并列注册在 lib/adapters/adapters.jsknownAdapters 映射中:

const knownAdapters = {
  http: httpAdapter,
  xhr: xhrAdapter,
  fetch: {
    get: fetchAdapter.getFetch,   // fetch 是唯一的“惰性获取”适配器
  },
};

注意 fetch 与另外两个适配器不同:它不是直接导出一个函数,而是导出了 getFetch(config) 工厂。这一点为后文“按 env 组合缓存适配器实例”打下了基础(见第 6 节)。

默认适配器选择顺序定义在 lib/defaults/index.js

adapter: ['xhr', 'http', 'fetch'],

也就是说,fetch 在默认链中排在最后:当运行环境既没有 xhr 也没有 http(例如某些边缘运行时,或经过 tree-shaking 的定制构建把这两个适配器裁掉了)时,axios 会自动回落到 fetch。如果你希望在所有环境中强制使用 fetch 适配器,必须显式设置 adapter: 'fetch' 来创建 axios 实例:

import axios from 'axios';

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

解析逻辑在 getAdapter() 中逐条尝试:名称型适配器通过 adapter.get(config) 求值,若返回 false 视为“环境不支持”,被记入拒绝原因;只有全部失败才会抛出 AxiosError.ERR_NOT_SUPPORT,错误信息会逐条列出每个适配器的失败原因("is not supported by the environment" 或 "is not available in the build"),见 lib/adapters/adapters.js

2. 功能面:与 xhr 适配器对齐的能力

官方文档明确:fetch 适配器支持与 xhr 适配器相同的功能,包括上传/下载进度捕获;此外还支持 xhr 没有的响应类型 streamformdata(前提是环境支持)。源码印证如下:

响应类型解析器。lib/adapters/fetch.js 中,factory 函数为五种响应类型注册了 resolver:

const resolvers = {
  stream: supportsResponseStream && ((res) => res.body),
};
// 依次为 text、arrayBuffer、blob、formData、stream 注册:
// 调用对应 res[type]() 方法;若 Response 实例不具备该方法则抛出
// AxiosError('Response type ' + type + ' is not supported', ERR_NOT_SUPPORT)

stream 类型直接返回 response.body(一个 ReadableStream),只有在 supportsResponseStream 检测通过时才会注册,因此文档才会强调“若环境支持”。

进度捕获。 上传与下载进度均通过 trackStream 包装请求/响应体流实现,按 64KB 分片逐块回调(DEFAULT_CHUNK_SIZE = 64 * 1024,见 lib/adapters/fetch.js)。进度事件字段由 lib/helpers/progressEventReducer.js 统一构造,回调参数包含:

{
  loaded,            // 已传输字节数
  total,             // 总字节数(已知时)
  progress,         // loaded / total
  bytes,            // 本次新增字节数
  rate,             // 估算速率
  estimated,        // 按当前速率估算的剩余时间
  event,            // 原始事件
  lengthComputable, // total 是否已知
  download: true,  // 或 upload: true,标识方向
}

单元测试 tests/unit/adapters/fetch.test.js 中的 progress 分组分别验证了 onUploadProgressonDownloadProgress 在本地 HTTP 服务器上的逐块回调行为(tests/unit/adapters/fetch.test.js),是这套能力可用性的直接证据。

3. 从 URL 读取 HTTP Basic 认证凭据

一个容易被忽略的行为:当配置中省略 auth 时,fetch 适配器会尝试从请求 URL 本身读取 Basic 认证凭据,例如 https://user:pass@example.com。具体规则(源码 lib/adapters/fetch.js):

  1. URL 编码凭据会被解码。Node.js 的 WHATWG URL 解析器返回的 username/password 是 percent-encoded 的,axios 在生成 Authorization 头之前先调用 decodeURIComponentSafe 解码,使 my%40email.com:passmy@email.com:pass 的形态进入 Basic 头;解码失败(非法编码)时回退为原始值,绝不抛出异常
  2. auth 配置优先。若同时提供了 auth 对象,URL 中的凭据会被忽略(测试用例 should prefer config auth over basic auth credentials from the request URL 验证了这一点);
  3. 凭据从 URL 中剥离。无论是否最终使用,URL 中的 user:pass@ 都会被清空(parsedURL.username = ''; parsedURL.password = ''),避免凭据出现在实际请求地址中;
  4. 编码使用 btoa(encodeUTF8(...))encodeUTF8lib/adapters/fetch.js 中对已废弃的 unescape(encodeURIComponent(str)) 模式的现代替代,可正确处理 UTF-8 多字节凭据(测试 should UTF-8 encode basic auth credentials from the request URL 覆盖)。

另外还有一个前置判断 maybeWithAuthCredentials(url):它只在协议分隔符 :// 之后的 URL 部分检测 @:,避免被 mailto: 等无协议分隔的 URL 中的冒号误判。最终生成的头是标准 Basic 方案:Authorization: Basic <base64(username:password)>,且会先删除已存在的 authorization 头再写入,保证配置值不会与 URL 凭据产生冲突。

4. 自定义 fetch(v1.12.0+):通过 env 注入运行时实现

v1.12.0 起,fetch 适配器不再只能使用环境的全局 fetch。你可以把自定义的 fetch 函数、RequestResponse 构造函数通过配置项 env 传入——这对提供自有 fetch 实现的框架(Tauri 平台层、SvelteKit 服务端 load 等)至关重要。

官方文档给出的行为约定:

  • 传入自定义 fetch 函数;Request/Response 可一并传入以匹配该实现;
  • 省略 Request/Response 时,使用环境的全局构造函数
  • 若你的自定义 fetch 与全局 Request/Response 不兼容,传 null 显式禁用它们
  • 注意:把 RequestResponse 都设为 null 后,fetch 适配器将无法捕获上传/下载进度(进度依赖流包装,而流包装依赖 Request 构造与 ReadableStream)。

源码中这套机制对应 lib/adapters/fetch.jsfactory 入口:

const factory = (env) => {
  const globalObject = /* utils.global 或 globalThis */;
  const { ReadableStream, TextEncoder } = globalObject;

  // 以 skipUndefined 合并:env 中显式传的 null 会覆盖全局值
  env = utils.merge.call({ skipUndefined: true },
    { Request: globalObject.Request, Response: globalObject.Response }, env);

  const { fetch: envFetch, Request, Response } = env;
  const isFetchSupported = envFetch ? isFunction(envFetch) : typeof fetch === 'function';
  const isRequestSupported = isFunction(Request);
  const isResponseSupported = isFunction(Response);

  if (!isFetchSupported) {
    return false;   // 无可用 fetch → 适配器声明“不支持”,交给 getAdapter 走下一个
  }
  // ...
};

关键细节:

  • skipUndefined 合并保证显式的 null 能覆盖全局构造函数——这正是文档中“传 null 禁用构造函数”的底层实现;
  • Request 可用时,每次请求会先构造 new Request(url, resolvedOptions)_fetch(request, fetchOptions);不可用时退化为 _fetch(url, options) 两参调用(lib/adapters/fetch.js);
  • Response 不可用时,stream 响应类型不注册、下载进度/maxContentLength 的流式检查跳过,适配为“无流”模式运行。

4.1 基本用法示例

import customFetchFunction from 'customFetchModule';

const instance = axios.create({
  adapter: 'fetch',
  onDownloadProgress(e) {
    console.log('downloadProgress', e);
  },
  env: {
    fetch: customFetchFunction,
    Request: null, // null -> disable the constructor
    Response: null,
  },
});

4.2 在 Tauri 中使用

Tauri 的 http 插件提供平台层 fetch,可绕过浏览器 CORS 限制(请求实际由原生层发出)。最小配置如下:

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');

这里只覆盖了 fetchRequest/Response 仍取全局实现,因此进度捕获照常工作。

4.3 在 SvelteKit 中使用

SvelteKit 的服务端 load 函数提供一个非标准fetch 实现,它负责 cookies 转发与相对 URL 解析,且不兼容标准的 URL/Request API。因此必须显式注入并把全局构造函数全部禁用:

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 };
}

注意此处是请求级覆盖(第二参数),与 Tauri 的实例级 env 等价——env 是普通配置项,遵循 axios 的实例配置与请求配置合并规则。

5. 请求组装的底层细节(源码视角)

读到这里,可以把 fetch 适配器一次请求的组装过程对照 lib/adapters/fetch.js 串起来,这些细节决定了它在不同运行时中的行为一致性:

fetchOptions 的“所有权”边界。 env 之外还有一个 fetchOptions 配置,可透传任意 fetch 选项(如 cacheintegrity)。但 axios 会先剔除 bodyheadersmethodsignalduplexcredentials 这六个键——它们由 axios 解析后的 Request/resolvedOptions 独占,防止用户传入的 fetchOptions 覆盖已解析的值(lib/adapters/fetch.js)。

默认请求选项。Request 可用时,axios 会补齐一组显式默认值(lib/adapters/fetch.js):

选项 默认值 说明
cache 'default' 不干预浏览器缓存策略
redirect 'follow' maxRedirects: 0 时被改写为 'manual'
referrer 'about:client' 不泄露 referrer
referrerPolicy '' 交由环境默认
mode 'cors' 跨域模式
integrity '' 未做 SRI 校验
keepalive false 不保活
priority 'auto' 调度优先级
window null 不绑定 window

这些默认值只填 resolvedOptions 中尚未出现的键,用户 fetchOptions 中同名项优先。

信号合成与超时。 请求的取消/超时统一经由 composeSignals([signal, cancelToken?.toAbortSignal()], timeout) 合成一个 AbortSignal 挂到 fetch 上(lib/adapters/fetch.js),因此 timeoutCancelToken、外部 AbortSignal 三种取消来源在 fetch 适配器下是同一套机制;出错时若合成信号带有 axios 的超时/取消错误(AxiosError 实例),会优先于 fetch 抛出的原始错误抛出,以保留“超时 vs 取消”的语义(lib/adapters/fetch.js)。

Cookie 行为。 withCredentials 非字符串值会映射为 'include'/'omit'(默认 'same-origin'),且仅在 Request.prototype 支持 credentials 时才传给 fetch——这是对 Cloudflare Workers 等环境的兼容处理(lib/adapters/fetch.js)。

请求体与上限检查。 发送前会做多重校验:data: URL 按解码后字节数预估并检查 maxContentLength;已知大小的请求体对照 maxBodyLength 预检;流式请求体(Node Stream/ReadableStream)则在 trackStream 逐块计数、超限时抛出 ERR_BAD_REQUEST,且刻意不信任调用方声明的 Content-Lengthlib/adapters/fetch.js)。响应侧对称地检查 maxContentLength:先做声明的 Content-Length 廉价预检,流式消费中再逐块累加校验,无 ReadableStream 的旧环境则回退到对物化结果的尺寸检查。

FormData 边界处理。 若请求体是 FormDataContent-Type: multipart/form-data 缺少 boundary,axios 会删除该头,让 fetch 在构造 Request 时自动生成带 boundary 的正确值(lib/adapters/fetch.js)。

6. getFetch 与按 env 组合的适配器缓存

最后一个值得注意的实现是适配器实例的复用策略。getFetch(config)[Request, Response, fetch] 三元组为键,在 Map 中逐级缓存 factory(env) 的产物(lib/adapters/fetch.js):

const seedCache = new Map();
export const getFetch = (config) => {
  let env = (config && config.env) || {};
  const { fetch, Request, Response } = env;
  const seeds = [Request, Response, fetch];
  // 沿 Map 链按 seed 逐级查找/创建
  // ...
};

含义是:同一组 env(同一套 fetch/Request/Response 实现)只会被 factory 初始化一次——特性探测(supportsRequestStreamsupportsResponseStream,即构造探测 Request/Response 的开销)只付出一次成本,后续请求直接复用已组装好的适配器闭包。测试文件末尾的 getFetch({ env: { fetch: uniqueFetch } }) 用例即验证了每个不同 fetch 实现对应独立的适配器实例。

factory 内部的两项特性探测值得单独说明(lib/adapters/fetch.js):

  • supportsRequestStream:构造一个带 ReadableStream 请求体且自定义 duplex getter 的 Request,确认该运行时既接受流式请求体、又不自动注入 Content-Type。只有探测通过,axios 才会用“包装请求体流”的方式实现上传进度与流式 maxBodyLength 检查;若环境支持 Request 但探测失败,axios 会对流式请求体抛出 ERR_NOT_SUPPORT(而不是静默降级);
  • supportsResponseStream:确认 new Response('').body 是真正的 ReadableStream,决定 stream 响应类型与下载进度是否可用。

7. 验证路径

fetch 适配器的完整行为由 tests/unit/adapters/fetch.test.js(约 1900 行,运行于 Node.js 的 fetch 环境)覆盖,与本文各节一一对应:

  • URL 凭据解码、UTF-8 编码、非法编码回退、auth 优先级、仅密码形式(should decode basic auth credentials from the request URL 等 6 个用例);
  • env 注入自定义/故障 fetch 的错误传播与信号传递(env: { fetch: safariFetch } 系列用例);
  • 上传/下载进度的逐块断言(progress 分组);
  • 请求头 CRLF 注入净化、原型污染防御等安全行为。

运行这些测试依赖仓库根目录的 vitest 配置(vitest.config.js),在本地检出仓库后可按贡献指南执行测试套件验证。

8. 小结

  • 启用axios.create({ adapter: 'fetch' }) 可在任何有 fetch 的环境强制使用该适配器;默认链 ['xhr', 'http', 'fetch'] 下它作为最后回退存在;
  • 能力:与 xhr 对齐的上传/下载进度,外加 streamformdata 响应类型(环境支持时);
  • 认证:省略 auth 时从 URL 读取 Basic 凭据,percent-decode 后生成 Authorization 头,auth 配置优先,凭据随后从 URL 中剥离;
  • 自定义实现:v1.12.0 起通过 env 注入 fetch/Request/Responsenull 表示禁用,但会失去进度捕获能力——Tauri 只需换 fetch,SvelteKit 需要同时禁用两个构造函数。

配套阅读:docs/es/pages/advanced/adapters.md(适配器总览)、docs/es/pages/advanced/progress-capturing.md(进度捕获专题)、docs/es/pages/advanced/cancellation.md(取消与超时)。

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