Skip to main content

Axios 原理及源码解析

一、Axios 总流程

Axios 的流程是调用 Axios.prototype.request 方法,在 dispatchRequest 请求派发前依次执行请求拦截器,在派发时对 config 配置进行转换处理,然后 http 请求适配器根据处理后的配置发起请求,在响应后依次执行响应拦截器,最终返回 promise

二、Axios 的构成

Axios 打印结果如下:

console.log({axios: axios})

Axios 的结构如下:

Axios 的核心是 request 方法,Axios 外部方法其实都是在调用这一个方法;方法内部创建一个 Promise 链式调用,常用的拦截器、数据修改器、http 请求等就是在这个 Promise 链式调用中逐步被执行。

三、Axios 源码解析

1、目录结构

Axios 源码目录结构如下:

📁 lib
|—— 📁 adapters // 定义发送请求的适配器 xhr、http
|—— |—— 📜 http.js // node 端使用的请求函数,实现 http 适配器
|—— |—— 📜 xhr.js // 浏览器端使用的请求函数,实现 xhr 适配器
|—— 📁 cancel // 定义取消功能
|—— |—— 📜 Cancel.js // 定义了取消请求返回的信息结构
|—— |—— 📜 CancelToken.js // 定义了用于取消请求的主要方法
|—— |—— 📜 isCancel.js // 判断是否是取消请求的信息
|—— 📁 core // 核心功能
|—— |—— 📜 Axios.js // Axios 构造函数,定义 request 方法
|—— |—— 📜 dispatchRequest.js // 用来调用 http 请求适配器方法,发送请求
|—— |—— 📜 InterceptorManager.js // InterceptorManager 类,拦截器管理器
|—— |—— 📜 mergeConfig.js // 合并参数配置项
|—— |—— 📜 settle.js // 根据 http 响应状态改变 Promise 的状态
|—— |—— 📜 createError.js // 生成指定的 error,抛出错误
|—— |—— 📜 enhanceError.js // 指定 error 对象的 toJSON 方法
|—— |—— 📜 transformData.js // 对响应以及请求进行格式化,改变数据格式
|—— 📁 helpers // 一些独立的辅助工具方法
|—— |—— 📜 ...
|—— 📜 axios.js // 对外入口
|—— 📜 defaults.js // Axios 默认配置
|—— 📜 utils.js // 公共工具方法

Axios 在默认配置中根据环境判断是浏览器还是 node 环境,如果是浏览器环境则使用 xhr 适配器,如果是 node 环境则使用 http 适配器,因此既支持浏览器中发送请求也支持 node 发送请求。

2、入口文件 lib/axios.js

Axios 入口通过 createInstance 创建出的实例和 axios.create() 创建出的实例一样。createInstance 流程大致为:

  1. 使用 Axios 函数创建上下文 context,包含自己的 defaultsconfig、管理拦截器的数组;
  2. 利用 Axios.prototype.request 和上下文创建实例 instance,实例为一个 request 发送请求的函数 this 指向上下文 context;
  3. 绑定 Axios.prototype 的其他方法到 instance 实例,this 指向上下文 context;
  4. 把上下文 context 中的 defaults 和拦截器绑定到 instance 实例。
// https://github.com/axios/axios/blob/v1.x/lib/axios.js
'use strict';

import utils from './utils.js'; // 引入 utils 对象,有很多工具方法
import bind from './helpers/bind.js';
import Axios from './core/Axios.js'; // 核心构造函数 Axios
import mergeConfig from './core/mergeConfig.js'; // 合并配置方法
import defaults from './defaults/index.js'; // 引入默认配置
import formDataToJSON from './helpers/formDataToJSON.js';
import CanceledError from './cancel/CanceledError.js';
import CancelToken from './cancel/CancelToken.js';
import isCancel from './cancel/isCancel.js';
import {VERSION} from './env/data.js';
import toFormData from './helpers/toFormData.js';
import AxiosError from './core/AxiosError.js';
import spread from './helpers/spread.js';
import isAxiosError from './helpers/isAxiosError.js';
import AxiosHeaders from "./core/AxiosHeaders.js";

// 创建 axios 实例
function createInstance(defaultConfig) {
// 实例化 Axios 对象
const context = new Axios(defaultConfig);
// 使实例指向 Axios.prototype.request 方法,上下文指向 context
// 也是调用 axios 即调用 Axios.prototype.request 函数的原因
const instance = bind(Axios.prototype.request, context);

// 复制 Axios.prototype、context 上的属性到实例上
// 也是为什么有 axios.get 等方法、defaults、interceptors 属性的原因
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
utils.extend(instance, context, null, {allOwnKeys: true});

// 工厂模式,可以根据自定义参数创建新的实例
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};

// 最后返回实例对象
return instance;
}

// 导出创建的默认实例
const axios = createInstance(defaults);

// 暴露 Axios class 以允许 class 继承,也就是兼容 new axios.Axios() 的用法
axios.Axios = Axios;

// 导出 Cancel 和 CancelToken
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
axios.toFormData = toFormData;

// Expose AxiosError class
axios.AxiosError = AxiosError;

// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError;

// 导出 all 和 spread API
axios.all = function all(promises) {
return Promise.all(promises);
};

axios.spread = spread;

// Expose isAxiosError
axios.isAxiosError = isAxiosError;

axios.AxiosHeaders = AxiosHeaders;

axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);

axios.default = axios;

// this module should only have a default export
export default axios

3、核心构造函数 lib/core/Axios.js

流程如下:

lib/core/Axios.js 中定义了 Axios 实例上的 request、get、post、delete 方法。get、post、delete 等方法均是基于 Axios.prototype.request 的封装。

Axios.prototype.request 中会依次执行请求拦截器、dispatchRequest(实际发起)、响应拦截器。

// https://github.com/axios/axios/blob/v1.x/lib/core/Axios.js
'use strict';

import utils from './../utils.js';
import buildURL from '../helpers/buildURL.js';
import InterceptorManager from './InterceptorManager.js';
import dispatchRequest from './dispatchRequest.js';
import mergeConfig from './mergeConfig.js';
import buildFullPath from './buildFullPath.js';
import validator from '../helpers/validator.js';
import AxiosHeaders from './AxiosHeaders.js';

const validators = validator.validators;

/**
* Create a new instance of Axios
*
* @param {Object} instanceConfig The default config for the instance
*
* @return {Axios} A new instance of Axios
*/
class Axios {
constructor(instanceConfig) {
// 默认参数
this.defaults = instanceConfig;
// 拦截器 请求和响应拦截器,详情见 Tab2
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

/**
* Dispatch a request
*
* @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
* @param {?Object} config
*
* @returns {Promise} The Promise to be fulfilled
*/
request(configOrUrl, config) {
// ... 核心方法,详情见 Tab3
return promise;
}

// 获取 Uri 的函数
getUri(config) {
config = mergeConfig(this.defaults, config);
const fullPath = buildFullPath(config.baseURL, config.url);
return buildURL(fullPath, config.params, config.paramsSerializer);
}
}

// 基于 Axios.prototype.request 封装其他方法
// 将 delete、get、head、options、post、put、patch 添加到 Axios.prototype的 原型链上
// 例如 Axios.prototype.delete = ...
// 就是为什么可以 axios.get 等别名的方式调用
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/

function generateHTTPMethod(isForm) {
return function httpMethod(url, data, config) {
return this.request(mergeConfig(config || {}, {
method,
headers: isForm ? {
'Content-Type': 'multipart/form-data'
} : {},
url,
data
}));
};
}

Axios.prototype[method] = generateHTTPMethod();

Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});

export default Axios;

Axios 拦截器和链式调用结构如下(点击放大查看)

  • 如果设置了多个请求响应器,后设置的先执行;
  • 如果设置了多个响应拦截器,先设置的先执行。

4、多种形式调用 Axios 的原理

Axios 的调用方式有:

// 方式一
axios(config)

// 方式二
axios.request(config)

// 方式三
axios.get(url[, config]) // delete, get, head, options 同理

// 方式四
axios.post(url[, data[, config]]) // post, put, patch 同理

// 方式五
axios('example/url'[, config])

通过上面 lib/axios.js 入口文件中以下代码实现:

lib/axios.js
function createInstance(defaultConfig) {
// 实例化 Axios 对象
const context = new Axios(defaultConfig);

// 使实例指向 Axios.prototype.request 方法,上下文指向 context
// 以兼容 axios(config) 的调用方式
const instance = bind(Axios.prototype.request, context);

// 复制 Axios.prototype,并绑定 this 为 context
// 以兼容 axios.request(config) 的调用方式
// 而 Axios.prototype.request 又定义了 get、post、option 等方法
// 因此也就兼容了 axios.get(url[, config])
// 也兼容了 axios.post(url[, data[, config]])
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});

// ...
}

可以看到,Axios 源码中使 Axios 实例对象指向 Axios.prototype.request 方法,兼容了函数调用的方式;另外还复制了 Axios.prototypecontext 上的属性到实例对象上,兼容了 axios.request、axios.get、axios.post 的调用方式;所以 Axios 既可以当函数调用,也可以当对象使用。

5、Axios 取消请求源码

点击查看 Axios 取消请求操作

Axios 可以通过传递 cancelToken 的配置来取消请求,其原理是创建一个额外的 Promise,在发送请求前对该 Promise 的状态进行监听,如果状态被修改,则在 dispatchRequest 请求本体抛出取消信息,在 adapter 适配器中取消请求,使 promise 走向 rejected

// https://github.com/axios/axios/blob/v1.x/lib/cancel/CancelToken.js
'use strict';

import CanceledError from './CanceledError.js';

// 通过 CancelToken 来取消请求操作
class CancelToken {
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}

// 创建一个 Promise
// 在调用 cancel 函数前该 promise 会一直处于 pending 状态
let resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});

const token = this;

// eslint-disable-next-line func-names
this.promise.then(cancel => {
if (!token._listeners) return;

let i = token._listeners.length;

while (i-- > 0) {
token._listeners[i](cancel);
}
token._listeners = null;
});

// eslint-disable-next-line func-names
this.promise.then = onfulfilled => {
let _resolve;
// eslint-disable-next-line func-names
const promise = new Promise(resolve => {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);

promise.cancel = function reject() {
token.unsubscribe(_resolve);
};

return promise;
};

// 把控制状态的方法 cancel 传给回调函数
// 这样外部就可以控制改变 CancelToken 的状态了
executor(function cancel(message, config, request) {
// 判断是否已经取消请求
if (token.reason) {
return;
}

// 创建取消请求的信息,并将信息添加到实例的 reason 属性上
token.reason = new CanceledError(message, config, request);

// 结束 this.promise 的 pending 状态
// 将 this.promise 状态设置为 resolve
resolvePromise(token.reason);
});
}

// 判断该请求是否已被取消,是则抛出异常
throwIfRequested() {
if (this.reason) {
throw this.reason;
}
}

/**
* Subscribe to the cancel signal
*/

subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}

if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
}

/**
* Unsubscribe from the cancel signal
*/

unsubscribe(listener) {
if (!this._listeners) {
return;
}
const index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
}

// 提供 CancelToken 对象和控制状态的方法 cancel
static source() {
let cancel;
const token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token, // 添加到请求的 config,用于标识请求
cancel // 调用 cancel 方法取消请求
};
}
}

export default CancelToken;