Wenzi

从 Axios 源码分析如何支持 fetch 方法的

蚊子前端博客
发布于 2024/10/29 09:40
数据请求库Axios源码中,是如何fetch方法的

Axios作为目前最火的 http 请求库,开发者们一直心心念希望它能支持 fetch 方法。终于在大概半年前,Axios 从 1.7.0 版本开始支持 fetch 方法了。目前(2024 年 10 月 28 日)Axios 的最新版本是1.7.7

1. 如何指定使用 fetch 方法 #

不过还是有一点要说明的是,Axios 现在虽然支持使用fetch()来发起网络请求了,但并不是作为默认的方法。在前端浏览器方面,目前默认的还是使用xhr。如果想使用 fetch 方法的话,需要手动指定。

// 指定所有环境均只使用 fetch 方法
axios({
  url: "https://www.xiabingbao.com",
  adapter: "fetch",
});

// 调整使用适配器的优先级,在当前环境中,按照数组顺序,优先支持哪种方法,就使用哪种
axios({
  url: "https://www.xiabingbao.com",
  adapter: ["fetch", "xhr", "http"],
});

手动指定使用适配器的方法有两种:

  1. 传入字符串,可以指定所有环境均只使用这一种方法,共有三种方式:"fetch", "xhr", "http";
  2. 传入数组,按照数组的顺序,当前环境支持哪种方式,则优先使用那个;

若想使用fetch()适配器时,不用再自己自定义适配器了,只需要指定或者提高 fetch 的优先级即可。

目前 Axios 默认支持的顺序是 ["xhr", "http", "fetch"]。因此,虽然 Axios 支持了 fetch 方法,但优先级较低。也可能在未来的某个时间点,Axios 会将其作为前端浏览器默认的请求方式。

2. 自定义适配器 #

如果 Axios 支持的三种方法都不满足我的业务需求,我在新版本里,还可以自定义适配器吗?比如支持 jsonp 请求、所在客户端专有的数据请求方式等。

可以的。

我在 2020 年时写过一篇文章如何实现 axios 的自定义适配器 adapter,那时候大概还是0.20.0版本。不过新版本里,自定义适配器的方式依然没变。

这里将adapter定义为 function,并返回 Promise 即可。

const request = Axios.create({
  adapter: (config) => {
    if (config?.params?.format === "jsonp") {
      return new Promise((resolve, reject) => {
        console.log(config);

        // 这里要 resolve() 什么数据,可以查看上面自定义适配器的文章
        resolve();
      });
    }

    // 这里需要将config.adapter设置为空
    // 否则会造成无限循环
    return Axios({ ...config, ...{ adapter: undefined } });
  },
});

3. 源码分析 #

在 Axios 内部,是如何判断在当前环境中,应当使用哪种适配器的。

Axios 提供了默认使用适配器的顺序["xhr", "http", "fetch"],若当前环境支持该适配器,则使用,否则继续向后查找。若所有适配器都不支持,则会抛出错误。

3.1 判断是否支持该适配器 #

在新版中,判断当前环境是否支持某适配器,放到了各适配器单独的文件中。

// https://github.com/axios/axios/blob/76dc3e68362c3881c75f032851a0f18cb04ffa7c/lib/adapters/xhr.js#L12

// 判断是否支持 xhr 适配器
const isXHRAdapterSupported = typeof XMLHttpRequest !== "undefined";

// 判断是否支持 fetch 适配器
const isFetchSupported = typeof fetch === "function" && typeof Request === "function" && typeof Response === "function";

// 判断是否支持 http 适配器
const isHttpAdapterSupported = typeof process !== "undefined" && utils.kindOf(process) === "process";

以后再扩展其他适配器时,也会比较方便。

3.2 选择当前使用的适配器 #

我们直接把源码粘过来,慢慢分析:

// https://github.com/axios/axios/blob/76dc3e68362c3881c75f032851a0f18cb04ffa7c/lib/adapters/adapters.js#L28
const knownAdapters = {
  http: httpAdapter,
  xhr: xhrAdapter,
  fetch: fetchAdapter,
};

export default {
  getAdapter: (adapters) => {
    /**
     * 循环遍历传入的适配器标识,若调用者不设置,则是Axios默认的:
     * ["xhr", "http", "fetch"]
     */
    adapters = utils.isArray(adapters) ? adapters : [adapters];

    const { length } = adapters;
    let nameOrAdapter;
    let adapter;

    const rejectedReasons = {};

    for (let i = 0; i < length; i++) {
      nameOrAdapter = adapters[i];
      let id;

      adapter = nameOrAdapter;

      /**
       * 若适配器标识既不是方法,也不为空,则判断是否是内置的适配器;
       * 若 adapter 是方法,则不进入判定,直接使用;即若我们自定义了适配器,
       * Axios 就不会再寻找当前要使用的内置适配器。
       */
      if (!isResolvedHandle(nameOrAdapter)) {
        // 通过字符串,去查找对应的适配器,比如若是xhr,则查找 xhrAdapter 等
        adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()];

        /**
         * 若传入的不是 ["xhr", "http", "fetch"] 这几种里的,
         * 则找不到内置的适配器,抛出异常
         */
        if (adapter === undefined) {
          throw new AxiosError(`Unknown adapter '${id}'`);
        }
      }

      /**
       * 若传入的是自定义适配器,或按优先顺序找到了内置适配器,并且当前环境也
       * 支持该适配器,则停止查找;
       * (若当前环境不支持找到的适配器,则adapter为false,会继续查找的)
       */
      if (adapter) {
        break;
      }

      rejectedReasons[id || "#" + i] = adapter;
    }

    // 若传入的adapter为null或者false,或者当前环境不支持所有内置的适配器,则抛出异常
    if (!adapter) {
      // 暂时省略提示错误的代码
    }

    return adapter;
  },
  adapters: knownAdapters,
};

在之前的版本中,Axios 是判断当前环境支持哪个适配器,就使用哪个适配。比如支持XMLHttpRequest方法,就使用 xhr;若支持process,则说明是在 Node.js 环境中,则使用 http。

但随着 fetch 适配器的加入,并不能再只判断当前环境是否支持某个适配器,因为可能同时多种适配器,只能考虑优先使用哪个适配器。

4. 总结 #

一般地,除非我们特别的场景,或者非常在意某个适配器时,可以通过 adapter 参数来调整或者自定义适配器。大部分情况下,Axios 版本的升级,对我们是无感的。

标签:axios
阅读(103)
Simple Empty
No data