首页
/ Axios 适配器机制详解:内置适配器选择、getAdapter 优先级解析与自定义适配器实战

Axios 适配器机制详解:内置适配器选择、getAdapter 优先级解析与自定义适配器实战

2026-09-04 22:43:54作者:翟萌耘Ralph

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'],

即默认顺序是 xhrhttpfetch。运行时逐个探测,第一个被当前环境支持(且在当前构建产物中可用)的适配器生效

  • 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 打上 nameadapterName 属性(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):

  1. 非数组输入先被规范化为数组,逐项遍历;
  2. 每一项若已是函数(或 null/false),视为已解析的句柄直接使用;否则按名称(转小写)在 knownAdapters 中查找,查不到直接抛出 AxiosError: Unknown adapter 'xxx'
  3. 名称对应的注册项若为带 get 的对象(即 fetch 适配器),则调用 adapter.get(config) 做环境探测;探测通过(返回函数)则立即 break 选中;
  4. 被跳过的适配器会被记入 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
    ));
  }
}

三个要点:

  1. 默认行为validateStatus 的默认值是"2xx 通过",因此 settle 对 2xx resolve、对其余状态码 reject——这与内置适配器的行为一致,官方文档也明确提示:想要非默认的成功状态区间,应使用配置项 validateStatus 定制,而不是在适配器里手判状态码;
  2. 错误码自动区分:4xx 生成 ERR_BAD_REQUEST,其余(如 5xx)生成 ERR_BAD_RESPONSE,配合 isAxiosError(err) && err.response 的判定模式可以直接用于错误处理;
  3. reject 时响应体不丢失AxiosError 构造时传入了 response,且 dispatchRequest.js 的拒绝分支也会对 error.response 执行 transformResponse,所以在 catch 分支中同样能拿到已转换(如 JSON 解析后)的错误响应体。

另外,config.signalAbortSignal)应像示例那样透传给底层传输,这样 CancelToken/AbortController 取消机制在自定义适配器中依然生效(取消检查见 lib/core/dispatchRequest.jsthrowIfCancellationRequested)。

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

与内置适配器行为的对照

从仓库源码结构看,内置适配器也严格遵循同一契约,可供自定义适配器参照:

小结

  • 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 与边缘运行时间无缝切换传输层,也为测试打桩和自定义传输协议留出了标准化的扩展点。

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