1.概览
用户实际使用的axios既是一个函数,也是对象。它本身是一个request函数,经过包装之后,身上又具有config和interceptors等诸多属性,这一点其实和jquery是完全一样的。axios支持两种请求,一种是ajax请求(浏览器环境),一种是http请求(node环境),分别对应xhrAdapter和httpAdapter这两个适配器
2.代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>axios</title>
<link
crossorigin="anonymous"
href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet"
/>
</head>
<body>
<div class="container">
<h2 class="page-header">axios取消请求</h2>
<button class="btn btn-primary">发送请求</button>
<button class="btn btn-warning">取消请求</button>
</div>
<script>
function Axios(config) {
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
Axios.prototype.request = function(config) {
let promise = Promise.resolve(config);
const chains = [dispatchRequest, undefined];
this.interceptors.request.handlers.forEach((item) => {
chains.unshift(item.fulfilled, item.rejected);
});
this.interceptors.response.handlers.forEach((item) => {
chains.push(item.fulfilled, item.rejected);
});
while (chains.length > 0) {
promise = promise.then(chains.shift(), chains.shift());
}
return promise;
};
function dispatchRequest(config) {
return xhrAdapter(config);
}
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
status: xhr.status,
statusText: xhr.statusText,
});
} else {
reject(new Error("request error: " + xhr.status));
}
}
};
if (config.cancelToken) {
config.cancelToken.promise.then((value) => {
xhr.abort();
reject(new Error("request cancel"));
});
}
});
}
function CancelToken(executor) {
let resolvePromise;
this.promise = new Promise((resolve) => {
resolvePromise = resolve;
});
executor(function() {
resolvePromise();
});
}
const context = new Axios({});
const axios = Axios.prototype.request.bind(context);
console.dir(axios, "axios request");
// debugger;
// 为什么这里还要将context的interceptor属性赋值给axios?
// 因为bind只是为了让request里的this能访问到interceptor,这里再赋值是为了暴露给用户使用拦截器
Object.keys(context).forEach((key) => {
axios[key] = context[key];
});
console.dir(axios, "axios request222");
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function(fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected,
});
};
axios.interceptors.request.use(
function one(config) {
console.log("请求拦截器 成功 - 1号");
return config;
},
function one(error) {
console.log("请求拦截器 失败 - 1号");
return Promise.reject(error);
}
);
axios.interceptors.request.use(
function two(config) {
console.log("请求拦截器 成功 - 2号");
return config;
},
function two(error) {
console.log("请求拦截器 失败 - 2号");
return Promise.reject(error);
}
);
// 设置响应拦截器
axios.interceptors.response.use(
function(response) {
console.log("响应拦截器 成功 1号");
return response;
},
function(error) {
console.log("响应拦截器 失败 1号");
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function(response) {
console.log("响应拦截器 成功 2号");
return response;
},
function(error) {
console.log("响应拦截器 失败 2号");
return Promise.reject(error);
}
);
const btns = document.querySelectorAll("button");
let cancel = null;
btns[0].onclick = function() {
if (cancel !== null) {
cancel();
}
let cancelToken = new CancelToken(function(c) {
cancel = c;
});
axios({
method: "GET",
url: "http://localhost:3000/posts",
cancelToken: cancelToken,
}).then((response) => {
console.log(response, "response");
cancel = null;
});
};
btns[1].onclick = function() {
cancel();
};
</script>
</body>
</html>
3.request
Axios.prototype.request = function(config) {
let promise = Promise.resolve(config);
const chains = [dispatchRequest, undefined];
this.interceptors.request.handlers.forEach((item) => {
chains.unshift(item.fulfilled, item.rejected);
});
this.interceptors.response.handlers.forEach((item) => {
chains.push(item.fulfilled, item.rejected);
});
while (chains.length > 0) {
promise = promise.then(chains.shift(), chains.shift());
}
return promise;
};
axios源码实现者是一个Promise使用大佬,核心都是Promise。首先创建了一个promise链,初始化是dispatchRequest(发请求)和undefined(占位),之后把请求拦截器的回调在链条前面加入,响应拦截器在链条后面加入,每次执行的过程都是从链条里取出连个回调进行操作,所以之前初始化的时候要有undefined占位,不然执行顺序就错乱了。在promise链执行的过程中,每一次的promise执行结果都会传到下一个执行的promise中。
4.CancelToken
function CancelToken(executor) {
let resolvePromise;
this.promise = new Promise((resolve) => {
resolvePromise = resolve;
});
executor(function() {
resolvePromise();
});
}
if (config.cancelToken) {
config.cancelToken.promise.then((value) => {
xhr.abort();
reject(new Error("request cancel"));
});
}
CancelToken本身创建了一个Promise,由用户通过参数传到发送ajax请求里,并在里头写入取消请求的逻辑,一旦用户通过暴露的resolvePromsie改变CancelToken里promise的状态,取消的逻辑便会执行