axios怎么实现请求取消?其实我在一开始用axios的cancelToken的时候,还是一脸茫然的,因为从操作来看,取消请求其实是一个操作,如果按照我们平时的思维,可能会考虑为把它写成Axios上的一个方法,直接调用就好了,毕竟在调起ajax的open()之后,是可以直接调abort()的。但是在调起ajax之前呢?那加一个请求状态来处理?然后取消请求之后后面的逻辑怎么走?好像多了不少逻辑。
我们先在看看cancelToken是怎么用的吧。
怎么操作的
这里直接用官方文档的用例来演示。
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 {
// handle error
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
上面这个例子可以看到,如果请求需要取消的话,可以在发请求的时候设置cancelToken这个配置,值是调用CancelToken.source()获得的对象中的token。然后这个token同时可以提供给不同的请求中。当需要取消请求时,可以调source.cancel()方法,这个方法可传入一个参数,这个参数最终会出现在被取消请求的catch中的error对象中。所以拿到相同token的请求都会被取消。
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})
});
// cancel the request
cancel();
上面这个用例是直接new一个CancelToken的实例,传入一个executor()方法,这个方法会得到一个c方法,我们可以在外部拿到这个c,然后在需要取消请求的时候调用它。
实现源码分析
在看上面两个用例的时候有没有观察到一些细节:如果有了解过Promise手写实现,那么第二个用例new一个CancelToken的实例的代码会看着很眼熟。我们改改看:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new Promise(function executor(resolve, reject) {
// An executor function receives a cancel function as a parameter
cancel = resolve; // reject参数弃去
})
});
// cancel the request
cancel(); // 相当于调resolve()
看起来像是那么回事。如果在结合第一个用例提及的调用cancel()方法可以把使用相同token的请求取消掉,这里的token如果就是Promise实例的话,那么调cancel()方法就会让token的then方法执行了。
好了,其实内部确实是基于Promise来实现的。
当new CancelToken(executor)时,内部就new一个promise实例,然后在将resolve保存起来。对于用户传入的executor方法,调这个方法并传入一个cancel()方法,这个方法接收一个message的参数,内部会用它去new一个Cancel实例,并调用resolve方法。这个Cancel实例其实只是用来保存取消信息。
/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @class
* @param {Function} executor The executor function.
*
* 支持通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token
* 相当于CancelToken实例会给executor函数传入一个cancel的方法,这个方法可以abort请求
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
/**
* 定义一个将来能执行取消请求的promise对象,当这个promise的状态为完成时(fullfilled),
* 就会触发取消请求的操作(执行then函数)。而执行resolve就能将promise的状态置为完成状态。
* 这里把resolve赋值给resolvePromise,就是为了在这个promise外能执行resolve而改变这个promise的状态
* 注意这个promise对象被赋值给CancelToken实例的属性promise,将来定义then函数就是通过这个属性得到promise
*/
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
/**
* 将CancelToken实例赋值给token
* 执行executor函数,将cancel方法传入executor,
* cancel方法可调用resolvePromise方法,即触发取消请求的操作
*
* Cancel实例其实是保存了CancelToken实例的取消状态
* 如果多次调用cancel方法,内部只会执行一次resolvePromise方法
*/
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
那还是看不出怎么取消请求的呢。这个就得到适配器中找源码了。这里我们找到/adapters/xhr.js,这里有判断是否有设置cancelToken,如果有,则给这个cancelToken.promise调then,在内部调abort()方法取消请求,并调reject()结束请求Promise,直接进入失败态,到响应拦截器是失败拦截函数中。
// 如果有设置cancelToken,就设置then中的处理
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
// 如果请求已经清空,就不再处理
if (!request) {
return;
}
// 取消请求,然后reject调Promise,再清空request
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
回过头来,CancelToken还实现了source()方法,这个方法也是new一个CancelToken实例,然后把实例作为token,cancel()方法作为cancel,整合成一个对象返回出去。
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
* 这个方法方便外部一次性拿到token跟cancel
* 然后把token设置给请求的cancelToken配置中,cancel用来需要取消请求时调用
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
结尾
关于axios请求取消的源码分析就到这结束了。下一篇会介绍一下axios的拦截器。