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。

相关推荐
weixin_382395233 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__3 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.4 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~5 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华6 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子8 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅8 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni9 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学10 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端