Axios源码笔记 | 抽丝剥茧,Core 核心处理引擎源码全解析

举报
叶一一 发表于 2025/06/24 09:18:57 2025/06/24
【摘要】 一、引言在前端开发的世界里,与后端服务器进行数据交互是必不可少的环节。Axios 作为一款强大且流行的 HTTP 客户端库,以其简洁的 API、出色的兼容性和丰富的功能,成为了开发者们处理 HTTP 请求的首选工具。而 Axios 的核心功能主要集中在 axios-1.x/lib/core 目录下,这里的各个模块协同工作,构成了 Axios 的核心处理引擎。理解这些模块的工作原理,不仅能让我...

一、引言

在前端开发的世界里,与后端服务器进行数据交互是必不可少的环节。Axios 作为一款强大且流行的 HTTP 客户端库,以其简洁的 API、出色的兼容性和丰富的功能,成为了开发者们处理 HTTP 请求的首选工具。而 Axios 的核心功能主要集中在 axios-1.x/lib/core 目录下,这里的各个模块协同工作,构成了 Axios 的核心处理引擎。理解这些模块的工作原理,不仅能让我们更加熟练地使用 Axios,还能从中学习到优秀的代码设计和架构思路。

接下来,就让我们一起揭开 Axios 核心处理引擎的神秘面纱。

二、Axios.js

2.1 核心代码

'use strict';

var utils = require('./../utils');
var buildFullPath = require('./buildFullPath');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = utils.merge(this.defaults, config);

  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

2.2 设计思路

Axios.js 是 Axios 的核心类,它的主要设计目标是封装 HTTP 请求的通用逻辑,提供一个统一的接口供开发者使用。通过创建 Axios 实例,开发者可以配置默认的请求参数,并利用拦截器对请求和响应进行预处理和后处理。同时,为常见的 HTTP 方法提供快捷调用方式,提高了开发效率。

2.3 重点逻辑

  • 配置合并:将用户传入的配置和实例的默认配置进行合并,确保每个请求都能使用正确的配置。
  • 拦截器管理:使用 InterceptorManager 管理请求和响应拦截器,通过 Promise 链式调用的方式,将拦截器按顺序执行。
  • 请求链构建:将请求拦截器、请求分发函数和响应拦截器依次添加到 chain 数组中,通过 Promise then 方法依次执行,实现请求和响应的完整处理流程。

2.4 流程全景


三、AxiosError.js

3.1 核心代码

'use strict';

var utils = require('./../utils');

function AxiosError(message, code, config, request, response) {
  Error.call(this);
  this.message = message;
  this.name = 'AxiosError';
  this.code = code;
  this.config = config;
  this.request = request;
  this.response = response;

  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, AxiosError);
  }
}

utils.inherits(AxiosError, Error);

AxiosError.ERR_BAD_OPTION_VALUE = 'ERR_BAD_OPTION_VALUE';
AxiosError.ERR_BAD_OPTION = 'ERR_BAD_OPTION';
AxiosError.ECONNABORTED = 'ECONNABORTED';
AxiosError.ETIMEDOUT = 'ETIMEDOUT';
AxiosError.ERR_NETWORK = 'ERR_NETWORK';
AxiosError.ERR_FR_TOO_MANY_REDIRECTS = 'ERR_FR_TOO_MANY_REDIRECTS';
AxiosError.ERR_DEPRECATED = 'ERR_DEPRECATED';
AxiosError.ERR_BAD_RESPONSE = 'ERR_BAD_RESPONSE';
AxiosError.ERR_BAD_REQUEST = 'ERR_BAD_REQUEST';
AxiosError.ERR_CANCELED = 'ERR_CANCELED';

AxiosError.from = function(error, config, code, request, response) {
  if (error instanceof AxiosError) {
    return error;
  }
  var axiosError = new AxiosError(error.message, code, config, request, response);
  axiosError.stack = error.stack;
  return axiosError;
};

module.exports = AxiosError;

3.2 设计思路

在 HTTP 请求过程中,可能会出现各种错误,如网络错误、请求超时等。AxiosError.js 定义了一个专门的错误类 AxiosError,用于统一管理和处理这些错误。通过继承 JavaScript 的原生 Error 类,并添加额外的属性,如 codeconfigrequest response,可以提供更详细的错误信息,方便开发者进行调试和错误处理。

3.3 重点逻辑

  • 错误继承:通过 utils.inherits 方法让 AxiosError 继承 Error 类的属性和方法,确保它是一个标准的错误对象。
  • 错误类型定义:预定义了多种常见的错误类型,方便开发者根据不同的错误类型进行针对性处理。
  • from 方法:用于将普通的错误对象转换为 AxiosError 对象,保持错误处理的一致性。

四、AxiosHeaders.js

4.1 核心代码

'use strict';

var utils = require('./../utils');

function normalizeHeaderName(headers, normalizedName) {
  utils.forEach(headers, function processHeader(value, name) {
    if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
      headers[normalizedName] = value;
      delete headers[name];
    }
  });
}

function AxiosHeaders(headers) {
  this.headers = headers || {};
}

AxiosHeaders.prototype.set = function(name, value) {
  if (typeof value === 'undefined') {
    return;
  }
  normalizeHeaderName(this.headers, name);
  this.headers[name] = value;
};

AxiosHeaders.prototype.get = function(name) {
  normalizeHeaderName(this.headers, name);
  return this.headers[name];
};

AxiosHeaders.prototype.delete = function(name) {
  normalizeHeaderName(this.headers, name);
  delete this.headers[name];
};

AxiosHeaders.prototype.toJSON = function() {
  return this.headers;
};

module.exports = AxiosHeaders;

4.2 设计思路

HTTP 请求头在请求和响应中起着重要的作用,不同的请求头可以控制请求的行为和响应的格式。AxiosHeaders.js 提供了一个专门的类 AxiosHeaders 来管理请求头,通过封装请求头的设置、获取和删除操作,避免了因请求头名称大小写不一致而导致的问题,提高了代码的健壮性。

4.3 重点逻辑

  • 请求头名称标准化normalizeHeaderName 函数将请求头名称统一转换为指定的大小写形式,确保在设置、获取和删除请求头时不会因大小写问题而出错。
  • 封装操作方法:提供了 setgetdeletetoJSON 等方法,方便开发者对请求头进行操作。

五、buildFullPath.js

5.1 核心代码

'use strict';

var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');

module.exports = function buildFullPath(baseURL, requestedURL) {
  if (baseURL && !isAbsoluteURL(requestedURL)) {
    return combineURLs(baseURL, requestedURL);
  }
  return requestedURL;
};

5.2 设计思路

在实际开发中,我们可能会为请求设置一个基础 URL,然后在每次请求时只提供相对路径。buildFullPath.js 提供了一个函数 buildFullPath,用于根据基础 URL 和请求 URL 构建完整的请求路径。这样可以方便开发者统一管理请求的基础 URL,提高代码的可维护性。

5.3 重点逻辑

  • URL 检查:使用 isAbsoluteURL 函数检查请求 URL 是否为绝对 URL。
  • URL 合并:如果基础 URL 存在且请求 URL 不是绝对 URL,则使用 combineURLs 函数将两者合并成完整的请求路径。

六、dispatchRequest.js

6.1 核心代码

'use strict';

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('./../cancel/isCancel');
var defaults = require('./../defaults');
var CanceledError = require('./../cancel/CanceledError');

module.exports = function dispatchRequest(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    if (config.cancelToken) {
      config.cancelToken.throwIfRequested();
    }

    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
};

6.2 设计思路

dispatchRequest.js 是 Axios 中实际发送请求的核心函数。它负责处理请求的取消检查、请求数据的转换、选择合适的适配器发送请求以及响应数据的转换。通过将这些逻辑封装在一个函数中,使得请求的发送过程更加清晰和可维护。

6.3 重点逻辑

  • 请求取消检查:在请求发送前后检查 cancelToken,如果请求已取消,则抛出 CanceledError
  • 数据转换:使用 transformData 函数对请求和响应数据进行转换,确保数据格式符合要求。
  • 适配器选择:根据配置选择合适的适配器(如 XHRhttp)发送请求,提高了 Axios 的可扩展性。

七、InterceptorManager.js

7.1 核心代码

'use strict';

var utils = require('./../utils');

function InterceptorManager() {
  this.handlers = [];
}

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

7.2 设计思路

拦截器是 Axios 非常强大的功能之一,它允许开发者在请求发送前和响应返回后对数据进行预处理和后处理。InterceptorManager.js 提供了一个 InterceptorManager 类,用于管理请求和响应拦截器。通过 use 方法添加拦截器,eject 方法移除拦截器,forEach 方法遍历拦截器,实现了拦截器的灵活管理。

7.3 重点逻辑

  • 拦截器存储:使用数组 handlers 存储所有的拦截器,每个拦截器包含 fulfilled rejected 两个处理函数。
  • 拦截器管理:提供 useejectforEach 方法,方便开发者添加、移除和遍历拦截器。

八、mergeConfig.js

8.1 核心代码

'use strict';

var utils = require('./../utils');

var mergeMap = {
  'url': 'configValue',
  'method': 'configValue',
  'params': 'configValue',
  'data': 'configValue',
  'baseURL': 'defaultValue',
  'transformRequest': 'merge',
  'transformResponse': 'merge',
  'paramsSerializer': 'configValue',
  'timeout': 'configValue',
  'timeoutMessage': 'configValue',
  'withCredentials': 'configValue',
  'adapter': 'configValue',
  'auth': 'configValue',
  'responseType': 'configValue',
  'xsrfCookieName': 'defaultValue',
  'xsrfHeaderName': 'defaultValue',
  'onUploadProgress': 'configValue',
  'onDownloadProgress': 'configValue',
  'decompress': 'configValue',
  'maxContentLength': 'configValue',
  'maxBodyLength': 'configValue',
  'maxRedirects': 'configValue',
  'socketPath': 'configValue',
  'httpAgent': 'configValue',
  'httpsAgent': 'configValue',
  'cancelToken': 'configValue',
  'signal': 'configValue',
  'validateStatus': 'merge',
  'headers': 'merge'
};

module.exports = function mergeConfig(config1, config2) {
  config2 = config2 || {};
  var config = {};

  function getMergedValue(target, source, strategy) {
    if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
      return utils.merge(target, source);
    } else if (utils.isPlainObject(source)) {
      return utils.merge({}, source);
    } else if (utils.isArray(source)) {
      return source.slice();
    }
    return source;
  }

  function mergeDeepProperties(prop) {
    if (!utils.isUndefined(config2[prop])) {
      return getMergedValue(config1[prop], config2[prop], mergeMap[prop]);
    } else if (!utils.isUndefined(config1[prop])) {
      return getMergedValue(undefined, config1[prop], mergeMap[prop]);
    }
  }

  utils.forEach(Object.keys(mergeMap), function(prop) {
    var merge = mergeMap[prop];
    if (merge === 'merge') {
      config[prop] = mergeDeepProperties(prop);
    } else if (merge === 'configValue') {
      config[prop] = typeof config2[prop] !== 'undefined' ? config2[prop] : config1[prop];
    } else if (merge === 'defaultValue') {
      config[prop] = typeof config1[prop] !== 'undefined' ? config1[prop] : config2[prop];
    }
  });

  var otherKeys = Object.keys(config2).filter(function(key) {
    return !mergeMap.hasOwnProperty(key);
  });
  otherKeys.forEach(function(key) {
    config[key] = getMergedValue(config1[key], config2[key], 'configValue');
  });

  return config;
};

8.2 设计思路

在使用 Axios 时,我们可以为每个请求单独配置参数,也可以为 Axios 实例设置默认配置。mergeConfig.js 提供了一个 mergeConfig 函数,用于合并两个配置对象,确保每个请求都能使用正确的配置。通过定义 mergeMap 来指定不同配置项的合并策略,使得配置合并更加灵活和可控。

8.3 重点逻辑

  • 合并策略:根据 mergeMap 中定义的策略,对不同的配置项采用不同的合并方式,如 configValuedefaultValue merge
  • 深度合并:对于对象和数组类型的配置项,使用 utils.merge 方法进行深度合并,确保数据的完整性。

九、settle.js

9.1 核心代码

'use strict';

var AxiosError = require('./AxiosError');

module.exports = function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  if (!validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(new AxiosError(
      'Request failed with status code ' + response.status,
      AxiosError.ERR_BAD_RESPONSE,
      response.config,
      response.request,
      response
    ));
  }
};

9.2 设计思路

在请求发送后,需要根据响应的状态码来决定是解析还是拒绝请求的 Promisesettle.js 提供了一个 settle 函数,通过 validateStatus 函数来判断响应状态码是否合法,从而决定 Promise 的状态。

9.3 重点逻辑

  • 状态码验证:调用 validateStatus 函数验证响应状态码,如果合法则解析 Promise,否则拒绝 Promise 并抛出 AxiosError

十、transformData.js

10.1 核心代码

'use strict';

var utils = require('./../utils');

module.exports = function transformData(data, headers, fns) {
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });

  return data;
};

10.2 设计思路

在请求发送前和响应返回后,可能需要对数据进行转换,如将 JavaScript 对象转换为 JSON 字符串,或者将 JSON 字符串转换为 JavaScript 对象。transformData.js 提供了一个 transformData 函数,用于依次调用传入的转换函数,对数据进行处理。

10.3 重点逻辑

  • 数据转换:遍历传入的转换函数数组,依次对数据进行处理,最终返回转换后的数据。

十一、结语

本文深入剖析了 Axios 1.x 版本中 axios-1.x/lib/core 目录下的所有核心模块。Axios.js 作为核心类,负责创建实例、管理拦截器和请求分发;AxiosError.js 定义了专用的错误类和错误类型,方便错误处理;AxiosHeaders.js 封装了请求头的管理操作;buildFullPath.js 用于构建完整的请求路径;dispatchRequest.js 负责实际发送请求并处理响应;InterceptorManager.js 实现了拦截器的灵活管理;mergeConfig.js 用于合并请求配置;settle.js 根据响应状态码决定 Promise 的状态;transformData.js 对请求和响应数据进行转换。这些模块相互协作,构成了 Axios 强大的核心处理引擎。

通过阅读源码,我们不仅了解了 Axios 的工作原理,还学习到了许多优秀的代码设计和架构思路。例如,使用拦截器模式实现请求和响应的预处理和后处理,利用 Promise 链式调用构建请求处理流程,以及通过模块化设计提高代码的可维护性和可扩展性。这些知识可以应用到我们自己的项目开发中,帮助我们写出更加健壮、高效的代码。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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