首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Axios 源码笔记 | 深入剖析 Adapters 适配器源码,揭开请求处理的神秘面纱

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

原创
作者头像
叶一一
发布2025-04-20 23:28:00
发布2025-04-20 23:28:00
6230
举报

一、引言

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

二、源码阅读与详细解析

2.1 adapters.js

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

2.1.1 关键代码

代码语言:javascript
复制
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 重点逻辑

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

2.1.4 架构全景

2.2 fetch.js

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

2.2.1 关键代码

代码语言:javascript
复制
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.2.4 架构全景

2.3 http.js

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

2.3.1 关键代码

代码语言:javascript
复制
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 的 httphttps 模块来发送 HTTP 请求。它将 Axios 的配置对象转换为 Node.js 请求所需的配置,并处理请求的发送和响应的接收。

2.3.3 重点逻辑

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

2.3.4 架构全景

2.4 xhr.js

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

2.4.1 关键代码

代码语言:javascript
复制
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、请求头和请求体。
  • 事件监听:监听 readystatechangeerrortimeout 事件,处理请求的成功、失败和超时情况。
  • 响应处理:将响应数据封装成 Axios 所需的响应对象。

2.4.4 架构全景

三、结语

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、引言
  • 二、源码阅读与详细解析
    • 2.1 adapters.js
      • 2.1.1 关键代码
      • 2.1.2 设计思路
      • 2.1.3 重点逻辑
      • 2.1.4 架构全景
    • 2.2 fetch.js
      • 2.2.1 关键代码
      • 2.2.2 设计思路
      • 2.2.3 重点逻辑
      • 2.2.4 架构全景
    • 2.3 http.js
      • 2.3.1 关键代码
      • 2.3.2 设计思路
      • 2.3.3 重点逻辑
      • 2.3.4 架构全景
    • 2.4 xhr.js
      • 2.4.1 关键代码
      • 2.4.2 设计思路
      • 2.4.3 重点逻辑
      • 2.4.4 架构全景
  • 三、结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档