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"],
});
手动指定使用适配器的方法有两种:
- 传入字符串,可以指定所有环境均只使用这一种方法,共有三种方式:"fetch", "xhr", "http";
- 传入数组,按照数组的顺序,当前环境支持哪种方式,则优先使用那个;
若想使用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 版本的升级,对我们是无感的。