一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
axios是一个基于promise的HTPP库,可用于浏览器和node中
这篇笔记只用于分析浏览器相关的代码
Features
- 在浏览器端使用 XMLHttpRequest 对象通讯
- 支持 Promise API
- 支持请求和响应的拦截器
- 支持请求数据和响应数据的转换
- 支持请求的取消
使用axios
axios({
url: "http://localhost:8090",
method: "post",
data: {
test: 1
}
})
axios.post('http://localhost:8090', { test: 1 }, { headers: "application/json" })
axios既支持当作方法调用,也可以通过axios.method方式调用
说明axios是一个函数也是一个对象
使用XMLHttpRequest来发起请求
axios底层是通过XMLHttpRequest这个API来发起请求
axios源码中通过new Axios()来创建一个对象,该对象的原型上挂载了很多方法
其中request方法是主要的方法,其它方法都是通过调用request来发起请求
class Axios {
request(config) {
// ... 上面都是一些例如 合并配置项 配置拦截器等等功能
let promise = Promise.resolve(config)
// 发起请求主要是dispatchRequest方法
return promise.then(dispatchRequest)
}
get() {}
post () {}
put() {}
...
}
function dispatchRequest(config) {
const { method = "get", url, headers, data } = config
let xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.onreadystatechange = function() {
if(xhr.status >= 200 && xhr.status < 300 && xhr.readystate === 4) {
// 请求成功
...
}
}
xhr.send(data)
}
所以无论是通过axios.method还是axios()发起请求,调用的都是dispatchRequest这个方法
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
axios源码中是通过var instance = Axios.prototype.request.bind(axios)
把axios绑定在上面的request方法中
在把var context = new Axios(defaultConfig)中的方法挂载到instance中
来实现把axios当作方法或者对象使用
拦截器设计与实现
使用方法
axios.interceptors.request.use((config) => {
//...
})
axios.interceptors.response.use((data) => {
// ...
})
此时我们可以看到,axios上有interceptors这个属性
这个属性中有request和response两个属性,里面又有use,eject两个方法,eject方法传入一个id,可以取消一个拦截器
当我们使用axios.interceptors.request.use时,表示添加一个请求拦截器,那很明显,我们需要一个地方来存储这些拦截器,其实是request和response中有个handlers中存储了拦截器,所以在这里我们可以获取到所有的拦截器.
我们来看看拦截器是怎么使用的,后添加的请求拦截器先触发,请求拦截器执行完了之后,就到dispatchRequest,再到响应拦截器顺序触发,我们必须保证顺序执行,那么就是使用Promise的链式调用
let chain = [request3, request2, request1, dispatchRequest, response1, response2, response3]
let promise = Promise.resolve(config)
while(chain.length) {
const { resolved, rejected } = chain.shift()
promise.then(resolved, rejected)
}
把请求拦截器、dispatchRequest和响应拦截器按照顺序放入一个数组chain中
声明一个fulfilled状态的Promise,把所有拦截器就放入该Promise的回调中,就能实现拦截器的功能了
eject
eject方法是取消某个拦截器,我们可以在使用use时,获取到该拦截器在数组的索引,然后再通过该索引来取消拦截器,例如设置为null
eject(id) {
if (this.interceptors[id] !== null) {
this.interceptors[id] = null
}
}
请求取消
取消功能底层是通过xhr.abort()来实现的,先来看看axios是怎么取消请求的
// 方法1:
let cancelToken = axios.CancelToken;
let cancel;
axios({
...
cancelToken: new cancelToken(c => {
cancel = c
})
})
cancel(); // 调用cancel方法即可取消请求
//方法2
let source = cancelToken.source();
axios({
...
cancelToken: source.token
})
source.cancel()
我们传入了一个cancelToken属性,再调用cancel方法就可以取消请求
第二种方法其实也是利用了第一种方法
原理
当我们发出请求之后,怎么能够调用xhr.abort()来取消请求呢,答案是通过Promise,我们先声明一个pending状态的Promise,当我们调用cancel方法之后,其实就是更改Promise的状态为fulfilled,此时去执行该Promise的回调,Promise的回调写死了就是调用xhr.abort()
传入的cancelToken其实就是一个Promise
//使用
let cancelToken = axios.CancelToken;
let cancel;
axios({
...
cancelToken: new cancelToken(c => {
cancel = c
})
})
// 实现
class CancelToken {
constructor(executor) {
let resolvePromise;
// 保存Promise的resolve方法
this.promise = new Promise(resolve => {
resolvePromise = resolve
})
executor(message => {
resolvePromise(message)
})
}
}
当我们new CancelToken时,就是创建了一个Promise,并保存了resolve方法,执行传入的函数executor,把更改Promise状态的方法抛出去,也就是外面得到的cancel方法。
function dispatchRequest() {
const { method = "get", url, headers, data, cancelToken } = config
let xhr = new XMLHttpRequest()
xhr.open(method, url, true)
if(cancelToken) {
cancelToken.promise.then(res => {
xhr.abort()
throw new Error(cancelToken.message)
})
}
xhr.onreadystatechange = function() {
if(xhr.status >= 200 && xhr.status < 300 && xhr.readystate === 4) {
// 请求成功
...
}
}
xhr.send(data)
}
判断是否存在cancelToken,如果存在的话,promise更改状态为fulfilled时,调用abort()方法,即可取消请求。
source原理
通过cancelToken.source()会得到一个对象source,其中有两个属性,一个cancel方法,一个token属性。
其中token跟new CancelToken()得到的是一样的
只是内部把new CancelToken(c => cancel = c)帮你放在了cancel中,并抛了出来,并没有不一样
let cancelToken = axios.CancelToken;
let source = cancelToken.source();
axios({
...
cancelToken: source.token // source.token === new CancelToken()
})
source.cancel()
// 实现
class CancelToken {
constructor(executor) {
let resolvePromise;
// 保存Promise的resolve方法
this.promise = new Promise(resolve => {
resolvePromise = resolve
})
executor(message => {
resolvePromise(message)
})
}
// sourc方法
static source() {
let cancel
let token = new CancelToken(c => {
cancel = c
})
return {
cancel,
token
}
}
}
我们可以看到,调用source方法只是帮你把cancel取出来了,只是一个语法糖
如果觉得对你有帮助,帮忙点个赞吧!
你们的点赞是我码字的动力!!!!