官网使用方式如下:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
axios.CancelToken
是一个函数,source()
是该函数静态方法(就是直接挂在函数上,通过函数名访问的),代码如下:
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
当调用source()
方法后,生成了CancelToken
对象,注意传递的 executor
函数,以及该函数的参数c
,c
可以控制CancelToken
函数内部的执行,将c
赋值给了外部变量,那么也就是将CancelToken
函数内部的执行状态交由外部控制
看看CancelToken
函数做了哪些事:
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
// 同样创建一个promise,并将其状态改变交由外部控制
resolvePromise = resolve;
});
// token 即为构造后的实例对象
var token = this;
// 先给该promise添加一个then回调方法
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
// 当 resolvePromise 执行时,先将内部的回调函数队列清空
// 至于这个队列怎么继续添加 then 回调函数,在 适配器 中
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});
// 重写该promise的then方法
this.promise.then = function(onfulfilled) {
var _resolve;
// 相当于包装了一层,添加了“订阅”处理
// 会先将“订阅”的回调函数执行,(上面会将 cancel 作为“订阅”函数的参数传入执行)
// “订阅”函数(也就是此处的 resolve)执行后,接着 then,调用外部传入的回调函数
var promise = new Promise(function(resolve) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
// executor 就是 source() 方法中的 executor;
// cancel 就是 c
// 当外部手动调用 source.cancel(message) 时
// **resolvePromise 执行,整个CancelToken函数内部的 promise 链也就执行了**
executor(function cancel(message) {
if (token.reason) {
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
再看一眼使用方式:
// post() 方法的第二个参数是 body,第三个参数是 options
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求
source.cancel('Operation canceled by the user.');
它是怎么将请求取消的呢?
适配器
在浏览器端创建XMLHttpRequest
实例发送网络请求,在Node
中使用平台内置的网络模块,主要看CancelToken
相关内容
// section1
...
var onCanceled;
// done 函数用于正常接受请求结束后,取消“订阅”的回调函数
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
// 订阅的回调函数在适配器发出请求时添加
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
request.abort(); // XMLHttpRequest自带的取消请求方法
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
if (!requestData) {
requestData = null;
}
// Send the request
request.send(requestData);
...
所以,当在外部手动调用source.cancel(message)
方法时,下列代码便会执行(也就是CancelToken
函数体内的代码)
// 先给该promise添加一个then回调方法
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
// 当 resolvePromise 执行时,先将内部的回调函数队列清空
for (i = 0; i < l; i++) {
token._listeners[i](cancel); // 队列中的回调函数依次执行
}
token._listeners = null;
});
那么又是如何判断source.cancel(message)
方法已经被调用了呢?
dispatchRequest.js
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new Cancel('canceled');
}
}
module.exports = function dispatchRequest(config) {
//
throwIfCancellationRequested(config);
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
...
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
...
}
return Promise.reject(reason);
});
...
}
throwIfCancellationRequested
函数的作用就是判断是否使用了cancelToken
,然后调用CancelToken
原型上的抛出错误的方法
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
可以看到if (this.reason)
存在,就抛出异常,那么reason
又是怎么添加的呢?
又回到CancelToken
函数体中
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
可知,cancel
函数执行,就添加了 reason
,而cancel
执行就是source.cancel()
小结
- 取消请求是在网络请求返回之前执行有效(当然啦,这不是废话嘛)
- 手动取消请求,是通过将
CancelToken
函数体内的执行情况交由外部控制(使用promise
,暴露resolve
方法),然后将“取消请求”的状态穿入到发出请求的函数dispatchRequest
中去截获状态