前言
axios是我们用的最多的请求库,不仅可以在浏览器端用,甚至还能在服务端用。下面让我们来探究一下它的源码
基本使用
// axios.create
const instance = axios.create({
baseURL: import.meta.env.VITE_URL
})
复制代码
下面让我们看看axios.create内部到底做了什么事!
// lib/axios.js
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig); // 创建了axios上下文
var instance = bind(Axios.prototype.request, context); // 创建实例
// 复制axios的原型到实例中
utils.extend(instance, Axios.prototype, context);
// 复制上下文到实例中
utils.extend(instance, context);
// 返回实例
return instance;
}
复制代码
可以看到instanceConfig就是我们传入的配置,它在内部与默认配置混合,并传给createInstance
然后instance上就有这么些属性了
拦截器
- 使用方式
// 请求拦截器
instance.interceptors.request.use(config => {
// 在这里可以对config进行操作
return config
})
// 响应拦截器
instance.interceptors.response.use((resp) => {
if(resp.data.status === 200) {
// 接口请求成功
return resp.data.data
}
return Promise.reject('')
})
复制代码
以上就是拦截器的基本使用,相信大家应该都懂
下面让我们来看看他的原理
在use的时候会把所有拦截器收集起来,等需要的时候调用
// lib/core/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
/**
* 往堆栈里面加入拦截器
* @param {Function} fulfilled promise为then下的处理函数
* @param {Function} rejected promise为catch下的处理函数
*
* @return {Number} 返回的是拦截器的id,可以用于移除这个拦截器
*/
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;
};
复制代码
请求
接下来我们就可以使用 axios 进行请求了
instance.get || instance.post || instance.request 等
复制代码
不管是哪种请求方式,本质上都是调用了request方法
我们要看的是在发送请求的时候,内部做了什么事情
// lib/core/Axios.js
Axios.prototype.request = function request(config) {
// 只保留核心逻辑代码
// 分别把请求拦截器和响应拦截器,以从后往前的顺序放入对应的数组中
var requestInterceptorChain = [];
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
// 保留一开始的对象
var newConfig = config;
// 把上面保存的请求拦截器数组拿出来 依次执行,得到一个最终的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()); // 把resolve和reject的响应拦截器放进来
}
// 返回最终的promise,.then的数据就是我们调用完请求的返回的那一个对象
return promise;
}
复制代码
取消请求之cancelToken
按照惯例我们来看一下基本使用
instance.interceptors.request.use(config => {
config.cancelToken = new axios.CancelToken((cancel) => {
setTimeout(() => {
cancel() // 取消请求
}, 10)
})
return config
})
复制代码
可以看到,调用cancel就可以取消掉本次请求了,是不是很神奇,想不想知道它是怎么做到的!!下面让我们来看看
// lib/cancel/CancelToken.js
/**
* @class
* @param {Function} executor 执行器,是一个函数
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 核心代码,可以看到他把resolve交给了这个变量,也就是把resolve交给这个变量来控制
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
// 这个cancel函数就是 new axios.CancelToken((cancel) => {}) 的那个cancel参数
// 可以看到,我们调用cancel的时候它才会去resolve,完全把resolve的控制权限交给了我们
const cancel = function cancel(message) {
if (token.reason) {
// 如果该次请求已经被取消了,返回
return;
}
token.reason = message;
resolvePromise(token.reason);
}
executor(cancel);
}
复制代码
但是,我们可以看到,这里并没有看到取消请求的任何操作,那么他到底在哪里取消请求的呢?待着这个疑问,我用cancelToken这个关键字搜索全局
终于找到了答案,在lib/adapters/xhr.js 312行 和 lib/adpaters/http.js 168行
xhr是axios在浏览器端请求的封装方法,http是在服务端的
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
if (req.aborted) return;
req.abort();
reject(cancel);
});
}
复制代码
then是在这里处理的,也就是我们调用cencel的时候,就会执行这个微任务,把这个请求取消掉了。
至此,我们常用的axios流程源码已经看完了。当然,axios的源码可不只这么点,这只是皮毛,还有很多东西等待你们去发现!!
下面让我们来封装一个通过的CancelToken类,基于TS,当然JS也可以,把类型去掉就行
封装CancelToken
import axios, { AxiosRequestConfig, Canceler } from 'axios'
export default class CancelToken {
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
private static pending: Map<string, Canceler> = new Map()
// 白名单, 写入接口名称
private static whiteRequest: string[] = []
/**
* 得到该格式的url
* @param {AxiosRequestConfig} config
* @returns
*/
private static getUrl(config: AxiosRequestConfig) {
return [config.method, config.url].join('&')
}
/**
* 添加请求
* @param {AxiosRequestConfig} config
*/
public static addPending(config: AxiosRequestConfig) {
const url = this.getUrl(config)
config.cancelToken = new axios.CancelToken(cancel => {
if (!this.pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去
this.pending.set(url, cancel)
}
})
}
/**
* 移除请求
* @param {AxiosRequestConfig} config
*/
public static removePending(config: AxiosRequestConfig) {
const url = this.getUrl(config)
const method = url.split('&')[1]
if (this.pending.has(url) && !this.whiteRequest.includes(method)) { // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
const cancel = this.pending.get(url)
cancel!(url)
this.pending.delete(url)
}
}
/**
* 清空 pending 中的请求(在路由跳转时调用)
*/
public static clearPending() {
for (const [url, cancel] of this.pending) {
cancel(url)
}
this.pending.clear()
}
}
复制代码
下面是使用方式
instance.interceptors.request.use(config => {
// 请求开始前,检查一下是否已经有该请求了,有则取消掉该请求
CancelToken.removePending(config)
// 把当前请求添加进去
CancelToken.addPending(config)
return config
})
instance.interceptors.response.use((resp: AxiosResponse<IResponse>) => {
// 接口响应之后把这次请求清除
CancelToken.removePending(resp.config)
if(resp.data.status === 200) {
// 接口请求成功
return resp.data.data
}
return Promise.reject('')
})
// router.ts
router.beforeEach((to, from, next) => {
// 路由跳转要清除之前所有的请求缓存
CancelToken.clearPending()
next()
})
复制代码
结语
感谢你们看到这里,希望你们能从这篇文章学到东西。
有错误或疑问欢迎评论区交流,再见!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END