我们在前后端交互的过程中,通常是通过请求接口来实现的,而一个页面中的交互又非常复杂,例如需要多次频繁请求同一个接口,或者在接口还没返回时就要切换路由等。这些都需要对接口请求的时机或者请求接口之后进行处理,避免一些无用的请求或者接口返回顺序的差异。 前两种方式,是在发起请求前进行控制,即控制发起请求的时机,而当请求发出之后则不再控制;而最终一种方式则是取消中断还在路上的请求,然后再发起一个新的请求,不用管发起的时机。这几种方式也要看业务的需要,选择最适合的即可。 我们在之前的如何实现 axios 的自定义适配器 adapter文章里,略过了 axios 是如何主动取消当前请求的。今天我们就将一下在 axios 中如何取消之前发起的请求,源码中又是怎样实现的。 我们先来看下 axios 中取消请求的用法: 从 demo 上来看,用法很简单,token 和 cancel 的关系对应上即可。 官方例子中还有一种取消请求的方式,这个我们放在后面讲,更容易理解一些。 取消请求的方法在 https://github.com/axios/axios/tree/master/lib/cancel 的目录中,3 个文件: 我们主要来看下 CancelToken 中的整个流程。 source 作为取消请求的入口,我们就先来看下 source 方法。 CancelToken 用来取消请求,但我理解起来,思路非常的绕,我们一点点来剖析: 也就是说会先创建一个 CancelToken 的实例 token,同时,将 CancelToken 中回调函数的参数给到了 cancel。当 cancel 执行时,则 token 中的 promise 属性则会从 pending 状态变为 fulfilled 状态,那么 promise 上挂载的 在调用 cancel 方法后,请求中是怎么操作的呢?我们看下adapter/xhr.js中的代码: 当我们使用 axios 的 catch 捕获内部抛出的异常时,就可以通过 现在再来看下 cancel 执行的整个流程,就会清晰流畅很多。 我们在第 1 节还留着一个问题,axios 取消请求还有另一种方式,即直接使用 CancelToken 类。 其实我们发现,source()方法,只是给我们额外又封装了一下,简单的返回了 token 和 cancel,但本质还是 CancelToken 中的东西。 在取消请求的过程中,token 要和 cancel 方法保持对应关系,即都在一个对象里;若其他的请求也要取消时,可以额外再生成一组 token 和 cancel。同时,这里还用到了 Promise 的一个机制,只有在当前 Promise 变更为 fulfilled 状态后,才能执行后面的 then 等操作。
1. 主动取消之前发起的请求 #
const CancelToken = axios.CancelToken;
// 返回两个字段,{ token, cancel }
// token用于表示某个请求,是一个Promise类型
// cancel是一个方法,当被调用时,则取消token注入的那个请求
const source = CancelToken.source();
axios
.get('/user/12345', {
cancelToken: source.token, // 将token注入到请求中
})
.catch(function (thrown) {
// 判断是否是因主动取消导致的
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
console.error(thrown);
}
});
axios.post(
'/user/12345',
{
name: 'new name',
},
{
cancelToken: source.token,
}
);
// 主动取消请求
// cancel方法会把注入的同一个token的请求方法一并取消掉
// 上面的get和post请求都会被取消掉
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
2. 源码解析 #
2.1 source 方法 #
// 创建token和cancel方法
CancelToken.source = function source() {
var cancel;
// token为 CancelToken 的实例,包含 promise 和 reason 两个属性
// 同时把 executor 中的参数给到 cancel
// 即CancelToken有一个回调函数,而这个回调函数的参数也是一个函数
// CancelToken怎么执行,我们接着看!
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel,
};
};
2.2 CancelToken #
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 创建一个Promise的实例,
// 当resolvePromise执行时,this.promise变为fulfilled状态
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
// new一个实例时,会立即执行CancelToken的回调函数executor方法
// executor的参数也是一个函数,即上面的cancel就是当前的cancel函数体
// 当executor的回调函数cancel执行时,会给当前CancelToken创建一个reason属性,这个属性是Cancel的实例
// 并执行resolvePromise方法,将reason实例穿进去;执行后this.promise变为fulfilled状态
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
then()
方法也就可以继续执行了。2.3 adapter #
if (config.cancelToken) {
// config.cancelToken就是上面创建的token
// 当token.promise变为fulfilled状态后,就可以执行后续的链式操作
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 取消当前的请求
request.abort();
// 将Cancel的实例cancel给到reject
reject(cancel);
// Clean up request
request = null;
});
}
isCancel
判断是否是因主动取消请求导致的异常:axios
.get('/user/12345', {
cancelToken: source.token, // 将token注入到请求中
})
.catch(function (thrown) {
// 判断是否是因主动取消导致的
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
console.error(thrown);
}
});
3. 取消请求的另一种方式 #
new CancelToken()
的返回值也是可以的;const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
// CancelToken创建的
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
}),
});
// cancel the request
cancel();
4. 总结 #
版权属于:
加速器之家
作品采用:
《
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
》许可协议授权
评论