axios是在实际项目中应用比较多的请求插件了,提供给我们的前置请求配置、响应拦截配置,让我们在实际应用中能够很好的利用;
为什么要看axios源码呢?
不知道大家有没有这种时候,当身边的其他人都在看源码实现时候,自己却无从下手,看着超级多的逻辑,逐渐失去了兴趣,因此我发现我自己的看源码切入点,“好奇”,看着项目配置中的“响应拦截”、“请求拦截”等,由心而生想要去知道这样到底是如何实现的呢? 带着这种“好奇”我打开了axios的源码。
在看源码之前,必要的内容是先过一遍axios的使用方法,这样带着问题,会记忆深刻
1.配置覆盖
axios的配置大概分为三种
1.1全局配置
即在插件内部的默认配置
- 其实更通俗而言就是默认的配置 默认配置均存在于default.js中,
1.2实例配置
在进行项目搭建时候,通常使用的自定义的全局配置 有的时候 在一个应用中需要实例化不同的
kxios对象,针对不同的接口 有利于管理,每个实例化对象度灰有自己的配置,可以通过全局配置进行初始化,或合并成一个新的配置项目,实例配置一般是我们进行自己配置的。
const nAxios = axios.create({
// baseURL: process.env.BASE_API,
// timeout: 5000,
withCredentials: true,
headers: {'X-Requested-With': 'XMLHttpRequest'}
})
1.3请求配置
在进行请求时候,对单个请求进行的配置 同一个实例会有一些公用的配置项目,如baseUrl,但是很多时候,不同的请求具体的配置是不一样的,如
url、method等,所以在请求的时候需要传入的配置与实例配置进行合并; 请求配置合并一般是这样子的?get请求的url ,后面的config都是我们传入的请求配置,传入到项目中后,会merge默认的配置,然后在进行请求;
kxios.get('http://localhost:4000/',{
baseURL:'http://localhost:4000/',
methods:'get',
headers:{
'Content-Type':'333',
'Type':333
}
}).then((res)=>{
console.log("请求后的then")
console.log(res)
})
1.4配置优先级
请求配置>实例配置> 全局配置
2.创建请求过程
2.1 创建axios
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
2.2创建axios实例
该文件提供了axios的实例构造器,即工厂函数
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// instance 绑定axios的默认数据 instance
var instance = bind(Axios.prototype.request, context);
// 复制axios的原型到实例中。此处主要是确保后续进行改变时候,不改变Axios的对象函数
utils.extend(instance, Axios.prototype, context);
//将实例绑定到Axios构造出得对象中
utils.extend(instance, context);
//instance 进行复制
return instance;
}
- 创建
Axios的实例 - 将
instance绑定axios的默认数据instance axios的原型到实例中。此处主要是确保后续进行改变时候,不改变Axios的对象函数- 将实例绑定到
Axios构造出得对象中 之所以选择这样,创建实例,然后在复制原型方法,是为了扩展axios本身更多的调用功能,在Axios的函数中,具有复杂数据类型,保证每个axios的实例在继承后都有独立的数据结构;
axios.get(url)
axios(url)
在外界引用了axios的方法就能进行创建axios的实例,并得到初始的默认值
进行调用
var nAxios = axios.create({
baseURL: process.env.BASE_API,
timeout: 5000,
withCredentials: true,
headers: {'X-Requested-With': 'XMLHttpRequest'}
})
//调用函数
nAxios.get('http:locallhost:888')
对于nAxios.get('http:locallhost:888')的执行的过程
- axios中进行调用get方法
- Axios.js中对
Axios的原型都附加上了请求方法,请求调用的时候直接调用时候,先进行merge请求的配置,在进行调用config的方法
2.3 merge 配置
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
2.4 进行请求适配配置
请求时候进行请求的调度,即去适配请求
- dispatchRequest.js的功能分析
2.5 发送请求的主要原因
在浏览器环境中主要是通过ajax进行请求
在浏览器中进行发送请求主要还是以来XMLHttpRequest
3.node可以使用axios的原因
我们在实际的使用中,发现node环境和浏览器环境都可以使用axios,其实是axios内部进行了适配;
- 在default.js 中,就进行了适配的操作
//获取默认的请求适配器
function getDefaultAdapter() {
var adapter;
//如果当前存在XMLHttpRequest 这个对象,则进行XHr的额适配器
if (typeof XMLHttpRequest !== 'undefined') {
// 浏览器环境下使用XML这个适配器
adapter = require('./adapters/xhr')
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// node 环境下使用http这个模块适配器
adapter = require('./adapters/http');
}
return adapter;
}
4.请求拦截器和响应拦截器原理
请求拦截器和响应拦截器帮助我们在实际工作中去处理自己前置转换需求
4.1 拦截器作用
- 当我们发送数据的时候,能够拦截到发送内容并进行更改内容,适用于统一性的发送信息,比如token、自定义的请求头,通过统一封装的request函数为每个请求添加统一的信息;
- 但后期如果需要为某些 GET 请求设置缓存时间或者控制某些请求的调用频率的话,我们就需要不断修改 request 函数来扩展对应的功能。此时,如果在考虑对响应进行统一处理的话,我们的 request 函数将变得越来越庞大,也越来越难维护
- 我们可以按照功能把发送 HTTP 请求拆解成不同类型的子任务,比如有用于处理请求配置对象的子任务,用于发送 HTTP 请求的子任务和用于处理响应对象的子任务。当我们按照指定的顺序来执行这些子任务时,就可以完成一次完整的 HTTP 请求。
4.2 拦截器构造函数
/axios/lib/core/InterceptorManager.js 在该文件中定义了拦截器的构造函数
InterceptorManager,用于响应拦截和请求拦截的使用
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;
}
};
4.3 创建请求拦截和响应拦截
应用在Axios的构造函数中
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(), //请求拦截器
response: new InterceptorManager() // 响应拦截器
};
}
4.4 拦截器执行过程
请求拦截器和响应拦截器主要实现原理为Promise的链式执行方法;
// 拦截器中间件
//dispatchRequest为默认的主要请求内容 dispatchRequest返回一个promise的请求对象
var chain = [dispatchRequest, undefined];
//让config配置promise中,用来层级传递配置
var promise = Promise.resolve(config);
//请求拦截器
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
//将请求拦截器插入到 chain数组的前面,插入的原理其实是 自定义配置时候越在后配置的先执行
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 响应拦截器 将响应拦截器push 到chain 的后面 这样会后执行
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
//chain的结构 =[请求拦截器1,请求拦截器2,主要请求,响应拦截器1,响应拦截器2]
//执行chain中的方法
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
这样就形成了一个拦截器的Promise队列
- 执行该队列结束请求
5.请求过程中的数据转换transformData
- 请求操作数据会在请求拦截器之前进行执行,而响应拦截器则会在响应拦截器之前进行调用,
- 执行在
promise的then中 dispatchRequest.js
5.1 transformRequest请求操作数据
5.2 响应操作数据transformResponse
转换函数
module.exports = function transformData(data, headers, fns) {
/*eslint no-param-reassign:0*/
utils.forEach(fns, function transform(fn) {
data = fn(data, headers);
});
//返回转换后的数据
return data;
};
6. 一个axios的流程
创建实例的时候+调用接口数据的时候
7.取消请求
取消请求的逻辑均在axios/lib/cancel中,取消请求在配置中主要使用CancelToken进行控制,在请求中,也存在专门为CancelToken设置的方法,
在xhr.js中
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
//停止当前的请求
request.abort();
//并且进行reject
reject(cancel);
// Clean up request
request = null;
});
}
cancelToken的方法 中主要的一个作用在于执行阻止请求,并进行取消函数,进行下一个then的调用
8.总结
看完axios ,自己收获还是蛮多的,更多的是流程设计
- 如何暴露更灵活的api供使用
- 巧妙利用Promise的then原理增加请求和拦截控制