取消未完成的Axios的异步请求
2020年11月5日 • ... • ☕️ 3 min read
开发时把网络速度调整到slow 3G,发现部分组件报了警告:
Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
造成这个问题对原因是,在已经unmounted的组件上,有异步回调函数触发,然后函数内部还有state的更新操作。解决问题也给的很清楚:在useEffect
的清理函数(返回的函数)里,取消异步任务的订阅。
那么,对于使用Axios库的项目,如果是已经发起的ajax请求,如何手动终止呢?顺着文档,找到了cancelToken
这个选项。
相关代码块
const CancelToken = axios.CancelToken;
// CancelToken.source()是工厂方法,返回一个包含token和cancel方法的对象
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 {
// handle error
}
});
// 手动取消请求
source.cancel('Operation canceled by the user.');
对于浏览器(区别于node),Axios创建的异步连接,本质上是新建了一个XMLHttpRequest
,然后包装Promise。
// Axios > lib > adapters > xhr.js
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
// ...
var request = new XMLHttpRequest();
// ...
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
// ...
// Send the request
request.send(requestData);
}
}
cancelToken.source
是个工厂函数,通过它,返回token实例和一个cancel处理函数。
// Axios > lib > cancel > cancelToken.js
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
可以看到,整体思路是在xhrAdapter
里新建了一个XHR连接,然后通过config.cancelToken.promise.then
的处理,来达到终止XHR连接(abort)的目的。
作为一个Promise,要触发then,这个Promise必须resolve,而这个resolve
,则应该是可以由用户触发的,才能达到目的。
CancelToken
这个构造函数,返回的实例,包含一个promise。
// Axios > lib > cancel > CancelToken.js
function CancelToken(executor) {
// ...
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
在source()
函数里,new CancelToken的时候,executor函数只有一个操作:cancel = c。
这里executor(function cancel(...) {...}))
,实际把整个函数赋值过去,即cancel = function cancel(...) {...})
。然后把这个cancel函数,连同CancelToken
实例,一起暴露给用户。即,必须在CancelToken
实例上执行对应的cancel函数,才会起效。
总结
所以梳理一下执行流程就是:
cancel -> resolvePromise -> then -> request.abort()
CancelToken流程
由于每个axios.get/post
都会生成一个对应的XHR连接,所以,每个连接都需要由source()
工厂函数,生成新的token和对应的cancel,否则,使用相同的token,调用cancel时,会把几个连接一起停掉,这一点需要注意。