您现在的位置是:首页 >技术教程 >手写axios源码系列一:axios核心知识点网站首页技术教程
手写axios源码系列一:axios核心知识点
最近从头搭建了一个vue小项目,想使用 axios 作为请求接口的第三方库。结果使用了 axios 这么长时间,想封装一下 axios ,也就是大部分项目中的 src/utils/request.js
文件,我居然无法默写出来,create 和 interceptors 这样的 api 都不知道怎么使用;所以决定深入一下 axios 源码,一探究竟 axios 到底是个什么东东。
当前 axios 源码版本为"axios": "^1.3.5"
axios的核心功能
1、axios 函数对象
为什么在这里叫 axios 函数对象 ?
这是因为在 JavaScript 中,函数也是对象,也可以挂载属性以及方法。俗话说:一切皆是对象。
axios使用方式:
axios({ method: "post" })
axios.post()
由 axios 的使用方式可知,axios 既可以被调用,也可以使用对象的点语法执行请求方法;所以 axios 既是函数又是对象。
来个例子:
// 声明axios函数
function axios(config) {
console.log("配置项:", config)
}
// 为axios挂载方法create
axios.create = () => {
console.log("axios.create方法")
}
// 为axios挂载属性get
axios.get = "get"
axios({ method: "post" })
axios.create()
console.log("axios的属性:", axios.get)
我们还可以打印一下 axios :
console.dir(axios)
可以看到 axios 是一个函数,且身上挂载有 create 方法以及 get 属性。
去看过 axios 的源码就知道,axios 本质上是一个函数,只是将 Axios 类的原型对象方法以及实例对象方法复制到了 axios 函数上面;还有一些其他的属性与方法也挂载到了 axios 函数对象上,如 CancelToken 类、Axios 类等。
2、dispatchRequest 发送请求
在axios中有两种请求方式,根据不同的平台(platform)来使用不同的请求方式:
- xhr(XMLHttpRequest),也就是常说的AJAX。(浏览器)
- http(Nodejs)
我们只来探究一下浏览器端的就好。
使用 axios 发送 http 请求时,其实底层调用的只有一个方法,就是 Axios.prototype.request
方法。Axios.prototype.request
方法又执行 dispatchRequest
方法,在 dispatchRequest
方法中使用 adapters.getAdapter
方法来区分是使用 xhr 还是 http 发送请求。
流程图:
dispatchRequest
模块是真正发送请求的地方:
function dispatchRequest(config) {
// 根据适配器获取发送请求的方式时xhr还是http
const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
// 返回promise对象
return adapter(config).then(
response => {
return response
},
err => {
throw new Error(err)
})
}
3、interceptors 拦截器
在项目中,一般发送 http 请求时会对请求和响应进行一些特殊处理:判断 token,设置请求头,统一处理响应错误信息等。如果要挨个对每个请求都做处理的话太麻烦,方便起见,axios 提供了拦截器。
拦截器分为:
- 请求拦截器
axios.interceptors.request
- 响应拦截器
axios.interceptors.response
请求拦截器的使用:
axios.interceptors.request.use(function (config) {
// 设置token
const token = sessionStorage.getItem("token");
if (token) { config.headers.token = token };
//返回config配置项
return config;
}, function (error) {
return Promise.reject(error);
})
响应拦截器的使用:
axios.interceptors.response.use(function (response) {
// 全局统一设置错误信息提示,code、msg是与后台约定好的返回值
const { code, msg } = response.data;
if (code === 0){
return response.data;
} else {
Notify({ type: 'danger', msg, duration: 5000 });
return Promise.reject(new Error(msg || 'Error'));
}
}, function (error) {
const { message} = error.response.data;
Notify({ type: 'danger', message, duration: 5000 });
return Promise.reject(error);
})
拦截器可以设置多个,例如:
// 请求拦截器1
axios.interceptors.request.use(function one(config){
console.log("请求拦截器 1");
return config;
},function one(error){
return Promise.reject(error);
})
// 请求拦截器2
axios.interceptors.request.use(function two(config){
console.log("请求拦截器 2");
return config;
},function two(error){
return Promise.reject(error);
})
// 响应拦截器1
axios.interceptors.response.use(function one(response){
console.log("响应拦截器 1");
return response;
},function one(error){
return Promise.reject(error);
})
// 响应拦截器2
axios.interceptors.response.use(function two(response){
console.log("响应拦截器 2");
return response;
},function two(error){
return Promise.reject(error);
})
当我发送一个请求时,会按照以下顺序打印出结果:
请求拦截器 2
请求拦截器 1
响应拦截器 1
响应拦截器 2
response
为什么会先打印请求拦截器 2
,然后再打印请求拦截器 1
呢?
这里就涉及到源码中拦截器的执行了。
源码 Axios.prototype.request 方法中定义了一条执行链 chain = [dispatchRequest, undefined]
。然后获取请求拦截器,使用array.unshift()
将其添加到执行链 chain
的头部;获取响应拦截器,使用 array.push()
将其添加到执行链的尾部,就形成了最终的执行顺序:
chain = [
two(config),
two(error),
one(config),
one(error),
dispatchRequest, // 发送请求
undefined,
one(response),
one(error),
two(response),
two(error),
]
4、cancelToken 取消请求
请求既可以发送,也可以进行取消,取消请求使用的是 xhr.abort()
方法,abort 就是中止
的意思。
取消请求的方式有两种:
-
使用 CancelToken(官方在 axios 版本
0.22.0
开始被弃用,但还是做了兼容处理,仍可使用)const CancelToken = axios.CancelToken; let source = CancelToken.source(); axios.get(url, { cancelToken: source.token }).then(response => { // ... }) // 取消请求 source.cancel();
-
使用 AbortController(官方推荐的使用方式,而且非常简单)
const controller = new AbortController(); axios.get(url, { signal: controller.signal }).then(response => { // ... }) // 取消请求 controller.abort();
AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求
下图为 AbortController 类:
可以看出,abort 为 AbortController 类的原型对象的方法,signal 是 AbortSignal 类生成的实例对象。
在 axios 源码中,取消请求封装了一个 CancelToken 类,使用发布/订阅模式进行取消请求的统一集中处理。在AJAX发送请求时,对取消请求 onCanceled 方法进行了订阅,在 CancelToken 内部使用 promise 对象向外暴露了 resolve 方法,在执行 source.cancel()
取消请求时,其实是执行了内部暴露的 resolve 方法,resolve 执行,则 promise 的状态由 pending 变为 fulfilled,然后 promise.then() 方法执行,而订阅的 onCanceled 方法就是放在 promise.then() 中去执行的。
// xhr.js
const xhr = new XMLHttpRequest();
onCanceled = () => {
xhr.abort();
}
config.cancelToken.subscribe(onCanceled)
// CancelToken.js
class CancelToken {
constructor(executor){
let resolvePromise;
this._listeners = [];
this.promise = new Promise(resolve => {
resolvePromise = resolve; // 向外暴露resolve方法
})
// executor执行器函数执行,传入参数为一个函数
executor(function c(){
resolvePromise(); // 执行resolve方法
})
this.promise.then(()=>{
let len = this._listeners.length;
while(len-- > 0){
this._listeners[i](); // 执行onCanceled()
}
})
}
subscribe(fn){
this._listeners.push(fn); //收集依赖
}
}
不理解不要紧,因为取消请求逻辑在我看来是 axios 源码里面最绕人,最难理解的知识点,因为这里使用到了设计模式、闭包、promise异步编程、高阶函数
等知识点,你只要有其中一个知识点模糊,估计就有点麻烦了,只能多学多看。
放张图理解一下: