1. 背景
我们在处理前后端交互的过程中,有时需要仔细斟酌接口的请求时机(例:频繁的Tab切换、树节点切换、数据录入时,请求什么时候发?)或接口返回数据的处理时机(例:接口还没返回时就要切换路由,路由都切换走了,之前请求的数据怎么办?),避免一些无用的请求或者接口返回顺序的差异(例如:同一个按钮点了多次,如果后点的先返回,先点的后返回,怎么办?)。
常见的处理方式有:
2. Axios 有“请求取消”技能
Axios 自带 cancel token API,支持“请求取消”技能
// CancelToken 的 source 工厂方法,构造出的对象含有:
// 1. token:一个 CancelToken 实例,即令牌
// 2. cancel: 一个用于取消令牌的函数。
const source = axios.CancelToken.source();
axios.get('/user/12345', {
// 将令牌实例(即 CancelToken)注入到 axios 内部
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
// 借助 isCancel 可判定该请求异常,是不是由“请求取消”引发
console.log('Request canceled', thrown.message);
} else {
// 其他类型请求异常处理...
}
});
// 外部可调用 cancel 函数,用于取消令牌;
// axios 内部观察到令牌被取消,随即对请求进行取消;
source.cancel('Operation canceled by the user.');
3. Axios 中的 CancelToken 什么原理?
3.1. 源码在哪?
Axios 的 CancelToken API 在源码中是一个独立模块。
我们先来看 CancelToken 怎么独立使用
再研究它如何与 Axios 结合
3.2. 怎么用
用法1:利用工厂构造令牌,且基于“订阅”的API
const CancelToken = require("./CancelToken");
const isCancel = require("./isCancel");
function handleCancel1(reason){
console.log("handleCancel1", "取消原因:", reason, isCancel(reason));
}
function handleCancel2(reason){
console.log("handleCancel2", "取消原因:", reason, isCancel(reason));
}
const {token, cancel} = CancelToken.source();
token.subscribe(handleCancel1); // 订阅
token.subscribe(handleCancel2); // 订阅
token.unsubscribe(handleCancel2) // 取消订阅
cancel("测试取消!"); // 取消令牌
用法2:利用工厂构造令牌,且基于“Promise”的API
const CancelToken = require("./CancelToken");
const isCancel = require("./isCancel");
function handleCancel1(reason){
console.log("handleCancel1", "取消原因:", reason, isCancel(reason));
}
function handleCancel2(reason){
console.log("handleCancel2", "取消原因:", reason, isCancel(reason));
}
const {token, cancel} = CancelToken.source();
token.promise.then(handleCancel1); // 订阅
const p = token.promise.then(handleCancel2); // 订阅
p.cancel(); // 取消订阅
cancel("测试取消!"); // 取消令牌
用法3:不使用工厂构造令牌
const CancelToken = require("./CancelToken");
const isCancel = require("./isCancel");
function handleCancel1(reason){
console.log("handleCancel1", "取消原因:", reason, isCancel(reason));
}
function handleCancel2(reason){
console.log("handleCancel2", "取消原因:", reason, isCancel(reason));
}
let cancel;
const token = new CancelToken((c)=>{
cancel = c;
});
token.subscribe(handleCancel1); // 订阅
token.subscribe(handleCancel2); // 订阅
token.unsubscribe(handleCancel2) // 取消订阅
cancel("测试取消!"); // 取消令牌
注意事项:在已取消的令牌上订阅的事件,会立即触发。
const CancelToken = require("./CancelToken");
const {token, cancel} = CancelToken.source();
console.log(new Date().toLocaleTimeString(), "[未取消]:订阅(取消事件1); 并将于5秒后,执行取消;");
token.subscribe((reason)=>{
console.log(new Date().toLocaleTimeString(), "[已取消]:触发(取消事件1)(取消原因:"+reason+"); 并将于5秒后, 在已取消前提下, 订阅(取消事件2);");
setTimeout(()=>{
console.log(new Date().toLocaleTimeString(), "[已取消]:订阅(取消事件2);");
token.subscribe((reason)=>{
console.log(new Date().toLocaleTimeString(), "[已取消]:触发(取消事件2);");
});
}, 5000)
});
setTimeout(()=>{
console.log(new Date().toLocaleTimeString(), "[未取消]:取消(取消原因:测试取消)");
cancel("测试取消!");
}, 5000)
3.3. 原理分析(CancelToken.js)
4. Axios 内部如何与 CancelToken 结合?
通过分析 CancelToken 的原理,
Axios 接收到外部传入的 CancelToken 令牌对象后,
只需要订阅令牌的取消事件,
并在取消事件被触发时,作出相应处理即可
订阅:
取消订阅:
5. Axios 与 Fetch API 的 AbortController?
FetchAPI 的 AbortController 可以粗略的理解为 W3C 官方提供的 CancelToken。
Axios 内部也提供了对 AbortController 的兼容处理:
参考:
https://github.com/axios/axios https://axios-http.com/