取消请求
取消请求可能不是多见的情况,一般情况下都没有处理
但是比如有三个tab,默认展示第一个tab,本应该展示第一个tab的内容,由于此时用户网速较慢,数据并未及时返回,同时用户点击了第二个tab,那么这时候用户点击的是tab2,但是看到的确还是tab1的内容
这会对用户造成困扰,所以需要取消不需要的请求
axios 使用 取消请求
从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) { //... }); // 取消请求 controller.abort()
(原先可以使用CancelToken,CancelToken 官网在v0.22已被废弃,推荐使用上文的AbortController)
AbortController
通过 AbortController() 构造函数来创建一个 controller 实例
然后通过 AbortController.signal 属性获取到它的关联对象AbortSignal 的引用
当 fetch request 初始化后,将 AbortSignal 作为一个选项传入请求的选项参数中(如下 {signal})。这将 signal,controller 与 fetch 请求关联起来,允许我们通过调用 AbortController.abort() 来取消 fetch 请求,正如下第二个事件监听器所示
var controller = new AbortController();
var signal = controller.signal;
var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');
downloadBtn.addEventListener('click', fetchVideo);
abortBtn.addEventListener('click', function() {
controller.abort();
console.log('Download aborted');
});
function fetchVideo() {
...
fetch(url, {signal}).then(function(response) {
...
}).catch(function(e) {
reports.textContent = 'Download error: ' + e.message;
})
}
简单来讲,使用AbortController 实例中的signal来对fetch进行绑定关联,调用实例的abort方法,即可把关联的请求执行取消操作
axios 源码关于取消请求
在核心方法xhr方法中,有这么一个判断
var xhr = function xhrAdapter(config) {
...
return new Promise(resolve,reject)=>{
var request = new XMLHttpRequest();
if (config.cancelToken || config.signal) {
onCanceled = function(cancel) {
// 已经取消过了
if (!request) {
return;
}
// 有 cancel 并且有 cancel type
reject(!cancel || cancel.type ? new CanceledError_1(null, config, request) : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
...
}
}
config.signal监听abort事件,回调onCanceled函数,如果外面执行了controller.abort();,onCanceled函数就会执行,在onCanceled内部执行request.abort方法同时把request变为null
graph TD
config.signal --监听--> abort
abort --回调-->onCanceled
onCanceled--执行-->reject & request.abort & request=null
click request.abort "https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/abort"
最终还是到了requset.abort上,它属于XMLHttpRequest静态方法,是XMLHttpRequest的取消请求方法
使用 CancelToken 构造函数取消请求不建议🐱
虽然说CancelToken已经不建议使用,但是它的思想还是挺有趣的
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
}); // 取消请求
cancel();
CancelToken的实例中接收一个函数executor,executor中的参数由外界变量cancel接收,只要执行cancel函数即可取消请求
分析CancelToken
- 首先是一个函数,可以接受一个函数
executor executor的函数注入实参cc应该也是一个函数- 执行
c函数就可以执行上文的取消请求方法
CancelToken 源码分析
function CancelToken(executor){
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
// 保留 this
var token = this;
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i = token._listeners.length;
while (i-- > 0) {
token._listeners[i](cancel);
}
token._listeners = null;
});
executor(function cancel(message, config, request) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new CanceledError_1(message, config, request);
resolvePromise(token.reason);
});
}
CancelToken 是一个高阶函数,接收executor函数,使用变量 resolvePromise拿到一个promise的resolve引用,resolvePromise执行,promise的then方法也会执行,那么then中都this._listeners都会执行
graph TD
this.promise=newPromise;
resolvePromise=newPromise.resolve
graph TD
CancelToken ----|接收参数executor| B(executor) %%内部函数cancel
B(executor)--执行-->resolvePromise执行
resolvePromise执行--触发-->this.promise.then执行
this.promise.then执行--执行-->this._listeners遍历执行
简化版CancelToken
function CancelToken(executor){
var resolvePromise;
this.promise = new Promise(resolve=>{
resolvePromise = resolve
})
this.promise.then((res)=>{
console.log("触发then函数",res)
})
executor(function innerCancel(message){
resolvePromise(message);
})
}
let cancel;
CancelToken(function outerCancel(c){
cancel = c
})
cancel("Rng")
-
外部 变量
cancel接收CancelToken的回调函数中的实参c, -
此时
outerCancel函数对应的是executor -
executor的实参innerCancel对应的是实参c, -
c又赋值给变量cancel,此时innerCancel和cancel是等价的 -
执行
cancel就是执行innerCancel -
执行
innerCancel执行resolvePromise -
执行
resolvePromise触发promise的then方法
现在变成了执行then方法的每一个_listeners方法
this指向CancelToken
是通过subscribe进行不断增加_listeners的函数数组
CancelToken.prototype.subscribe
CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
在上文中的xhr方法中,已经执行了一次
function xhr(){
...
if (config.cancelToken || config.signal){
config.cancelToken && config.cancelToken.subscribe(onCanceled);
}
...
}
最后还是落到了onCanceled身上
所以使用了数组这种结构,可以同时取消多个使用同一个
cancelToken关联的请求
CancelToken.source
这也可以取消请求,是上文的变体
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('/user/12345',
{ name: 'new name' }, {
cancelToken: source.token
})
source.cancel('Operation canceled by the user.');
CancelToken.source源码
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
这个cancel是那个promise的resolve方法的引用,执行cancel,相当于执行promise
source.token还是那个cancelToken的实例
写在最后
通过此次源码分析,学到了
- 高阶函数
executor - promise的灵活运用,
resolve触发,就自动执行then方法,体现了发布订阅的这种设计模式的优势 - fetch 和xhr的取消请求,他们触发模式不一样,fetch是真正的
sigal关联,axios是模拟这种写法 - source 的这个变体,提供了很大的灵活性,用的都是一个
CancelToken.source(),用的也是同一个CancelToken.token,一次可以取消多个请求,使用构造函数的这种写法,可以对单个请求进行单独处理
time:2022/10/3