Axios用cancelToken取消未完成的异步请求
有些问题,可能由于网速提升太快,慢慢被我们淡忘,比如雪碧图、连接合并这些优化。但是另外还有些容易忽略的问题,比如对于发起的ajax请求,有时需要手动终止,尤其是react的组件,有时在unmounted组件上没清理异步调用,就会碰到警告。
开发时把网络速度调整到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
这个选项。
相关代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// 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处理函数。
1 2 3 4 5 6 7 8 9 10 11 |
// 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 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函数,才会起效。
总结
所以梳理一下执行流程就是:
1 |
cancel -> resolvePromise -> then -> request.abort() |
CancelToken流程
由于每个axios.get/post
都会生成一个对应的XHR连接,所以,每个连接都需要由source()
工厂函数,生成新的token和对应的cancel,否则,使用相同的token,调用cancel时,会把几个连接一起停掉,这一点需要注意。
Comments: 0