从 Github 上把 Axios 项目的 master 分支拷贝到本地,用编辑器打开项目目录
首先我们先解析一下整个 Axios 项目的一些关键的文件结构
//对照项目的文件目录,梳理一下其中的一些关键文件夹以及它的作用
// axios源码总体结构
.
├── dist # 存放压缩过后的axios代码
├── examples # 存放axios方法应用示例
├── lib # 存放axios核心代码
├── sandbox # 搭建模拟服务器
// axios核心代码目录结构
.
├── lib
|—— adapters # 适配器(用于包装http和xhr对象)
|—— http.js # http适配
|—— xhr.js # xhr对象适配
|—— cancel # 取消操作实现(即用于取消发送的http请求,CancelToken)
|—— core # Axios对象构建以及其核心方法实现
|—— Axios.js # Axios的构造函数文件
|—— buildFullPath.js # 构建完整的URL路径函数
|—— createError.js # 构建Error对象
|—— dispatchRequest.js # 发送请求函数,axios就是调用此函数来发送http请求
|—— enhanceError.js # 用来更新错误对象内部的配置数据, createError函数内部通过调用此函数最终生成指定信息的Error对象
|—— InterceptorManager.js # Axios对象的拦截器相关方法
|—— mergeConfig.js # 合并配置项内容,Axios对象中通过此方法来合并默认的请求头信息和传入的新的相关头信息
|—— settle.js # 根据响应状态码改变Promise状态为resolve()成功状态或reject()失败状态
|_ transformData.js # 格式化请求或响应的数据
|—— env # 存放axios版本信息,文件内容会随着版本自动更新
|—— helpers # 存放一些用于axios内部的通用方法以及模块
|—— axios.js # 入口文件
|——default.js # axios对象内部的默认值
|——utile.js # 存放通用方法
接下来我们从入口文件 index.js 开始,梳理一下一个完整的 axios 创建的过程
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
var axios = createInstance(default)
通过 axios.js 入口文件,就可以创建出一个默认的可以使用的 axios 对象,这里顺便提一下位于同一层级的 default.js 和 utils.js 两个文件,default.js 文件的作用就是用于生成一个默认的可以引用的配置项,即我们在 axios.js 中向 createInstance()方法传入的 default 参数,而 utils.js 文件则是存放了一些用于处理数据的公共方法,如特定的 forEach()遍历方法以及 merge()合并参数的方法。
我们通过 axios.js 中的 createInstance(defaultConfig)可以了解到,之所以我们使用 axios()可以发送请求并且通过传参的方式来实现对于请求的配置,本质上就是通过 Axios 对象以及其原型链上的 Axios.prototype.request()方法来实现的,那么接下来,我们接着分析一下 Axios.js 这个实现请求发送的核心文件以及其中的方法
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 = mergeConfig(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 requestInterceptorChain = [];
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
if (
typeof interceptor.runWhen === "function" &&
interceptor.runWhen(config) === false
) {
return;
}
synchronousRequestInterceptors =
synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(
interceptor.fulfilled,
interceptor.rejected
);
});
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var promise;
if (!synchronousRequestInterceptors) {
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
var newConfig = config;
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break;
}
}
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
while (responseInterceptorChain.length) {
promise = promise.then(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
);
}
return promise;
};
这里补充一个注意点,在真正使用 axios 的拦截器方法的时候,通过打印执行的结果可知,请求拦截器在执行上是逆序执行的,也就是其输出结果是从下往上依次输出的,而响应拦截器在执行上是顺序执行的,也就是其输出结果是从上往下依次输出。
出现这样的情况的原因在于:在 Axios.js 文件中对于拦截器方法的处理是分两种情况的,对于请求拦截器而言,其加入请求拦截器数组是使用 unshift()方法,也就是不断添加在数组的头部,类似栈的方式,最后再将完整的请求拦截器数组再通过 unshift()追加到 chain 数组的头部,chain 数组也就是包含请求拦截器,发送请求的 dispatchRequest()方法和响应拦截器的最终完整的执行数组。
而对于响应拦截器而言,其每一个拦截器项都是通过 push()方法推入数组的,类似队列的方式,最终也是通过 concat()方法将响应拦截器数组拼接到 chain 数组的末尾,所以最终就呈现出请求拦截器逆序输出,而响应拦截器顺序输出的情况
通过上述对于 Axios.js 文件的解析,我们可以看到,这个文件里 Axios.prototype.request()也就是我们最终可以实现发送请求的本质函数做的事情总结起来无非是以下几点:
1. 处理请求所需的配置项,将默认配置项和传入的新参数进行合并,最终生成完整的配置项config
- 创建请求拦截器数组,并且通过 unshift()依次将我们使用的拦截器函数推入数组,形成一个请求拦截器数组等待遍历执行
- 创建 chain 数组,添加两个数组项,chain[0]为 dispatchRequest()方法,也就是实现请求发送的核心方法,chain[1]为 undefined,用于占位符,之所以需要添加一个占位符的原因在于在遍历数组执行都是通过 promise.then()方法来实现的,而对于 then()方法而言,默认是拥有两个参数的,第一个是 onFulfilled 表示成功的回调,而第二个是 onRejected 表示失败的回调,而我们将 disptachRequest()方法作为成功的回调后,需要一个占位符来作为失败回调
- 创建响应拦截器数组,并且通过 push()依次将我们使用的拦截器函数推入数组,形成一个响应拦截器数组等待遍历执行
- 判断是否存在请求拦截器,如果存在,则将请求拦截器数组拼接到 chain 数组头部,然后遍历整个 chain 数组输出请求拦截器方法以及发送请求方法,最终返回一个 promise,也就是在使用时我们可以通过 axios().then()来获取请求结果的原因
- 遍历输出响应拦截器
接下来我们对其中两个非常重要的方法进行解析,一个是拦截器函数的实现,一个是请求发送的 dispatchRequest()方法的实现
首先是拦截器函数
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null,
});
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);
}
});
};
然后是请求发送的 dispatchRequest()方法
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new Cancel("canceled");
}
}
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
config.headers = config.headers || {};
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
utils.forEach(
["delete", "get", "head", "post", "put", "patch", "common"],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(
function onAdapterResolution(response) {
throwIfCancellationRequested(config);
response.data = transformData.call(
config,
response.data,
response.headers,
config.transformResponse
);
return response;
},
function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
}
);
};
到这里,我们从入口文件开始一步一步将一个 axios 对象的创建过程梳理完成,可以看到,虽然我们最终使用的 axios 看起来非常的简单,但是其内部是对原生 AJAX 做了非常多且复杂的封装,最终才暴露出一个简洁易懂的请求发送的方法,其实对于整个 axios 项目,还有关于适配器方法的处理相关的adapters文件夹
以及其中存在的两个适配器文件http.js
和xhr.js
,而处于同级的cancel文件夹
则是用来实现取消请求发送的,而在helpers文件夹
中,作者封装了大量的适用于 axios 库本身可以使用的公共函数
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)