Axios原理与实现机制详解(一)底层请求适配机制

Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 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。

相关推荐
阴阳怪气乌托邦几秒前
别再啃OA代码了!低代码"搭积木"式搞数智化,我直接少写500行
前端·低代码
beelan5 分钟前
v-on的思考
前端
山河木马8 分钟前
前端学习C++之:.h(.hpp)与.cpp文件
前端·javascript·c++
用户9272472502198 分钟前
PHP + CSS + JS + JSON 数据采集与展示系统,支持伪静态
前端
努力只为躺平12 分钟前
一文搞懂 Promise 并发控制:批量执行 vs 最大并发数,实用场景全解析!
前端·javascript
Web小助手14 分钟前
大保剑:Promise的有趣体验
javascript
李大玄14 分钟前
Google浏览器拓展工具 "GU"->google Utils
前端·javascript·github
爱编程的喵15 分钟前
从DOM0到事件委托:揭秘JavaScript事件机制的性能密码
前端·javascript·dom
蓝倾20 分钟前
京东批量获取商品SKU操作指南
前端·后端·api
JSLove27 分钟前
常见 npm 报错问题
前端·npm