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。

相关推荐
_r0bin_1 小时前
前端面试准备-7
开发语言·前端·javascript·fetch·跨域·class
IT瘾君1 小时前
JavaWeb:前端工程化-Vue
前端·javascript·vue.js
zhang98800001 小时前
JavaScript 核心原理深度解析-不停留于表面的VUE等的使用!
开发语言·javascript·vue.js
potender1 小时前
前端框架Vue
前端·vue.js·前端框架
站在风口的猪11082 小时前
《前端面试题:CSS预处理器(Sass、Less等)》
前端·css·html·less·css3·sass·html5
程序员的世界你不懂2 小时前
(9)-Fiddler抓包-Fiddler如何设置捕获Https会话
前端·https·fiddler
MoFe12 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
去旅行、在路上3 小时前
chrome使用手机调试触屏web
前端·chrome
Aphasia3113 小时前
模式验证库——zod
前端·react.js
lexiangqicheng4 小时前
es6+和css3新增的特性有哪些
前端·es6·css3