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。

相关推荐
低代码布道师1 小时前
第二部分:网页的妆容 —— CSS(下)
前端·css
一纸忘忧1 小时前
成立一周年!开源的本土化中文文档知识库
前端·javascript·github
涵信1 小时前
第九节:性能优化高频题-首屏加载优化策略
前端·vue.js·性能优化
前端小巷子2 小时前
CSS单位完全指南
前端·css
SunTecTec2 小时前
Flink Docker Application Mode 命令解析 - 修改命令以启用 Web UI
大数据·前端·docker·flink
软件技术NINI2 小时前
html css js网页制作成品——HTML+CSS甜品店网页设计(4页)附源码
javascript·css·html
涵信2 小时前
第十一节:性能优化高频题-响应式数据深度监听问题
javascript·vue.js·性能优化
codingandsleeping3 小时前
Express入门
javascript·后端·node.js
Vaclee3 小时前
JavaScript-基础语法
开发语言·javascript·ecmascript
拉不动的猪3 小时前
前端常见数组分析
前端·javascript·面试