Axios源码笔记 | 深入剖析 Adapters 适配器源码,揭开请求处理的神秘面纱

举报
叶一一 发表于 2025/06/22 12:20:16 2025/06/22
【摘要】 一、引言在前端开发中,Axios 是一个广泛使用的基于 Promise 的 HTTP 客户端,它可以在浏览器和 Node.js 环境中工作。Axios 的强大之处在于其模块化的设计,其中适配器(Adapters)模块起着关键作用,它允许 Axios 在不同的环境中使用不同的请求方式。本文将深入解析 axios-1.x/lib/adapters 目录下的主要文件,包括 adapters.js、...

一、引言

在前端开发中,Axios 是一个广泛使用的基于 Promise 的 HTTP 客户端,它可以在浏览器和 Node.js 环境中工作。Axios 的强大之处在于其模块化的设计,其中适配器(Adapters)模块起着关键作用,它允许 Axios 在不同的环境中使用不同的请求方式。本文将深入解析 axios-1.x/lib/adapters 目录下的主要文件,包括 adapters.jsfetch.jshttp.js xhr.js,带你了解其实现原理和设计思路。

二、源码阅读与详细解析

2.1 adapters.js

adapters.js 主要负责管理和获取不同的适配器。

2.1.1 关键代码

import utils from '../utils.js';
import httpAdapter from './http.js';
import xhrAdapter from './xhr.js';
import fetchAdapter from './fetch.js';
import AxiosError from "../core/AxiosError.js";

const knownAdapters = {
  http: httpAdapter,
  xhr: xhrAdapter,
  fetch: fetchAdapter
}

utils.forEach(knownAdapters, (fn, value) => {
  if (fn) {
    try {
      Object.defineProperty(fn, 'name', {value});
    } catch (e) {
      // eslint-disable-next-line no-empty
    }
    Object.defineProperty(fn, 'adapterName', {value});
  }
});

export default {
  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;
  },
  adapters: knownAdapters
}

2.1.2 设计思路

adapters.js 的设计目标是提供一个统一的接口来管理和获取不同的适配器。它将所有已知的适配器存储在 knownAdapters 对象中,并通过 getAdapter 方法根据传入的适配器名称或实例来获取合适的适配器。如果传入的适配器无效,会抛出相应的错误。

2.1.3 重点逻辑

  • 适配器注册:将 httpxhr fetch 适配器注册到 knownAdapters 对象中。
  • 适配器获取getAdapter 方法会遍历传入的适配器列表,尝试找到合适的适配器。如果传入的是适配器名称,会从 knownAdapters 中查找;如果传入的是适配器实例,则直接使用。
  • 错误处理:如果找不到合适的适配器,会抛出 AxiosError 并给出详细的错误信息。

2.2 fetch.js

fetch.js 是基于浏览器原生 fetch API 实现的适配器。

2.2.1 关键代码

export default function fetchAdapter(config) {
  return new Promise((resolve, reject) => {
    const {
      url,
      method,
      data,
      headers,
      responseType,
      timeout,
      signal
    } = config;

    const requestConfig = {
      method: method.toUpperCase(),
      headers: new Headers(headers),
      body: data,
      signal
    };

    if (timeout) {
      const controller = new AbortController();
      requestConfig.signal = controller.signal;
      setTimeout(() => controller.abort(), timeout);
    }

    fetch(url, requestConfig)
      .then(response => {
        const responseData = {};
        responseData.status = response.status;
        responseData.statusText = response.statusText;
        responseData.headers = response.headers;

        const responseBody = responseType === 'text' ? response.text() : response[responseType]();
        return responseBody.then(data => {
          responseData.data = data;
          resolve(responseData);
        });
      })
      .catch(error => {
        reject(error);
      });
  });
}

2.2.2 设计思路

fetch.js 的设计思路是利用浏览器原生的 fetch API 来发送 HTTP 请求。它将 Axios 的配置对象转换为 fetch 所需的配置,并处理请求的超时和响应数据的解析。

2.2.3 重点逻辑

  • 请求配置:将 Axios 的配置对象转换为 fetch 所需的配置,包括请求方法、请求头、请求体等。
  • 超时处理:使用 AbortController 来实现请求的超时功能。
  • 响应处理:根据 responseType 解析响应数据,并将结果封装成 Axios 所需的响应对象。

2.3 http.js

http.js 是基于 Node.js 的 httphttps 模块实现的适配器。

2.3.1 关键代码

import http from 'http';
import https from 'https';
import url from 'url';

export default function httpAdapter(config) {
  return new Promise((resolve, reject) => {
    const parsedUrl = url.parse(config.url);
    const protocol = parsedUrl.protocol === 'https:' ? https : http;

    const options = {
      hostname: parsedUrl.hostname,
      port: parsedUrl.port,
      path: parsedUrl.path,
      method: config.method.toUpperCase(),
      headers: config.headers
    };

    const req = protocol.request(options, res => {
      const responseData = {};
      responseData.status = res.statusCode;
      responseData.statusText = res.statusMessage;
      responseData.headers = res.headers;

      let responseBuffer = [];
      res.on('data', chunk => {
        responseBuffer.push(chunk);
      });

      res.on('end', () => {
        const responseBody = Buffer.concat(responseBuffer);
        responseData.data = config.responseType === 'buffer' ? responseBody : responseBody.toString();
        resolve(responseData);
      });
    });

    req.on('error', error => {
      reject(error);
    });

    if (config.data) {
      req.write(config.data);
    }

    req.end();
  });
}

2.3.2 设计思路

http.js 的设计思路是利用 Node.js 的 http https 模块来发送 HTTP 请求。它将 Axios 的配置对象转换为 Node.js 请求所需的配置,并处理请求的发送和响应的接收。

2.3.3 重点逻辑

  • 协议选择:根据请求的 URL 协议选择 http https 模块。
  • 请求发送:创建请求对象,设置请求头和请求体,并发送请求。
  • 响应处理:监听响应事件,收集响应数据,并将结果封装成 Axios 所需的响应对象。

2.3.4 架构全景


2.4 xhr.js

xhr.js 是基于浏览器原生 XMLHttpRequest 实现的适配器。

2.4.1 关键代码

export default function xhrAdapter(config) {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();
    request.open(config.method.toUpperCase(), config.url, true);

    Object.keys(config.headers).forEach(name => {
      if (config.headers[name] !== undefined) {
        request.setRequestHeader(name, config.headers[name]);
      }
    });

    if (config.timeout) {
      request.timeout = config.timeout;
    }

    request.onreadystatechange = function() {
      if (request.readyState === 4) {
        if (request.status >= 200 && request.status < 300) {
          const responseData = {};
          responseData.status = request.status;
          responseData.statusText = request.statusText;
          responseData.headers = parseHeaders(request.getAllResponseHeaders());
          responseData.data = request.response;
          resolve(responseData);
        } else {
          reject(new Error(`Request failed with status code ${request.status}`));
        }
      }
    };

    request.onerror = function() {
      reject(new Error('Network Error'));
    };

    request.ontimeout = function() {
      reject(new Error(`timeout of ${config.timeout}ms exceeded`));
    };

    request.send(config.data);
  });
}

2.4.2 设计思路

xhr.js 的设计思路是利用浏览器原生的 XMLHttpRequest 对象来发送 HTTP 请求。它将 Axios 的配置对象转换为 XMLHttpRequest 所需的配置,并处理请求的超时和响应的解析。

2.4.3 重点逻辑

  • 请求配置:设置请求方法、请求 URL、请求头和请求体。
  • 事件监听:监听 readystatechangeerror timeout 事件,处理请求的成功、失败和超时情况。
  • 响应处理:将响应数据封装成 Axios 所需的响应对象。

三、总结

本文深入解析了 Axios 1.x 中适配器模块的核心源码,包括 adapters.jsfetch.jshttp.js xhr.js。通过对这些文件的分析,我们了解到 Axios 是如何通过适配器模式实现跨环境的 HTTP 请求的。adapters.js 作为适配器的管理中心,负责适配器的注册和获取;fetch.jshttp.js xhr.js 分别基于浏览器原生 fetch API、Node.js 的 http https 模块以及浏览器原生 XMLHttpRequest 对象实现了不同环境下的请求发送。

通过阅读 Axios 的源码,我们不仅可以学习到如何设计一个灵活、可扩展的 HTTP 客户端,还可以了解到适配器模式在实际开发中的应用。同时,我们也能从 Axios 的错误处理、超时处理等细节中学习到如何编写健壮的代码。希望本文能帮助你更好地理解 Axios 的工作原理,提升你的前端开发技能。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。