Axios 是一个基于 promise 网络请求库,作用于
node.js
和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.jshttp
模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
通过官网这个简短介绍,可以看出axios能在nodejs和浏览器分别使用http和XHR,也就是所谓isomorphic同构的。
我们来看一下这块源码实现:
arduino
const knownAdapters = {
http: httpAdapter, // Node.js 环境
xhr: xhrAdapter, // 浏览器环境
fetch: fetchAdapter // 支持 Fetch API 的环境
}
Axios 在 lib/adapters/adapters.js 中定义了一个适配器系统,预先注册了三种适配器。
每个适配器都有其特定的使用场景,axios如何做到选择什么适配器呢我们继续往下探究:
ini
getAdapter: (adapters) => {
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;
if (!isResolvedHandle(nameOrAdapter)) {
adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()];
if (adapter === undefined) {
throw new AxiosError(`Unknown adapter '${id}'`);
}
}
if (adapter) {
break;
}
rejectedReasons[id || '#' + i] = adapter;
}
if (!adapter) {
const reasons = Object.entries(rejectedReasons)
.map(([id, state]) => `adapter ${id} ` +
(state === false ? 'is not supported by the environment' : 'is not available in the build')
);
let s = length ?
(reasons.length > 1 ? 'since :\n' + reasons.map(renderReason).join('\n') : ' ' + renderReason(reasons[0])) :
'as no adapter specified';
throw new AxiosError(
`There is no suitable adapter to dispatch the request ` + s,
'ERR_NOT_SUPPORT'
);
}
return adapter;
},
系统会按照优先级顺序尝试使用适配器,如果适配器可用(返回 true),就使用该适配器,如果所有适配器都不可用,抛出错误。
那怎么来判断环境呢,分别查看三个适配器的环境检测代码:
浏览器环境检测:
检测 XMLHttpRequest 对象是否存在,这是最基础的浏览器 HTTP 请求对象,几乎所有现代浏览器都支持。
javascript
// xhr.js - 浏览器环境检测
const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
export default isXHRAdapterSupported && function (config) {
// XHR 适配器实现
}
Node.js 环境检测:
检测 process 对象是否存在,使用 utils.kindOf() 进一步确认是否为 Node.js 的 process 对象,避免在浏览器环境中误判。
arduino
// http.js - Node.js 环境检测
const isHttpAdapterSupported = typeof process !== 'undefined' &&
utils.kindOf(process) === 'process';
export default isHttpAdapterSupported && function httpAdapter(config) {
// HTTP 适配器实现
}
Fetch API 环境检测:
检测 fetch 函数是否存在,检测 Response 对象是否存在,检测 ReadableStream 是否存在,确保完整的 Fetch API 支持。
javascript
// fetch.js - Fetch API 环境检测
const isFetchSupported = typeof fetch !== 'undefined' &&
typeof Response !== 'undefined' &&
typeof ReadableStream !== 'undefined';
export default isFetchSupported && function fetchAdapter(config) {
// Fetch 适配器实现
}
再次回到adapters.js,把每一行注释。
javascript
export default {
// 导出对象,包含 getAdapter 方法
getAdapter: (adapters) => {
// 定义 getAdapter 函数,接收 adapters 参数(可能是单个适配器或适配器数组)
adapters = utils.isArray(adapters) ? adapters : [adapters];
// 检查 adapters 是否为数组
// 如果是数组,保持不变
// 如果不是数组,将其转换为单元素数组
// 这样确保后续处理统一使用数组形式
for (let i = 0; i < length; i++) {
// 遍历适配器数组,尝试找到一个可用的适配器
// length 应该是 adapters.length
nameOrAdapter = adapters[i];
// 获取当前遍历到的适配器,可能是适配器名称字符串或适配器函数
let id;
// 声明 id 变量,用于存储适配器名称
adapter = nameOrAdapter;
// 假设 nameOrAdapter 是一个适配器函数,直接赋值给 adapter
if (!isResolvedHandle(nameOrAdapter)) {
// 检查 nameOrAdapter 是否不是函数(或 null 或 false)
// isResolvedHandle 函数判断参数是否为函数或特殊值
adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()];
// 如果不是函数,则认为是适配器名称字符串
// 1. 将 nameOrAdapter 转换为字符串,并赋值给 id
// 2. 将 id 转换为小写
// 3. 从 knownAdapters 对象中获取对应的适配器函数
}
if (adapter) {
// 检查是否成功获取到适配器函数
break;
// 如果成功获取到适配器,跳出循环,不再继续尝试后续适配器
}
}
return adapter;
// 返回找到的适配器函数
// 如果没有找到任何适配器,返回 undefined
}
}
这样我们就明白了axios适配器的自动选择。
接下来讨论优先级。
在 lib/defaults/index.js 中40行设置默认优先级: ['xhr', 'http', 'fetch']。
优先尝试使用 XMLHttpRequest,其次是 Node.js http,最后尝试 fetch API。