我们会怎么做? 在理解axios的CancelToken之前,我们不妨想想,换做自己,如何去取消一个已经发出的http请求?
毫无疑问,我们都会想到XMLHttpRequest原型上的方法abort,用它就可以终止请求了。
但,axios内部是依靠promise实现的一套完整的发出请求-接收请求以及取消请求机制。我们都知道,promise只有三种状态,pending(等待),resolved(成功),rejected(失败)。promise本身并没有“取消”或说“终止”一说,也没有这种状态,也即一个已经从pending状态执行的promise只有成功或失败。那么,如何用同一个promise去取消自己呢?(其实自己取消不了自己的,除非包装变魔法旧瓶装新酒😈)
所以,取消请求这也是axios源码中比较绕的一块,它的本质就是在解决如何取消一个已经执行的promise本身。
话不多说,在我们自己掌控一个promise实例的运行时,可以给该promise包装一层来取消它:
function wrap(p) { //对promise进行包装
let abort;
const rejectPromise = new Promise((resolve, reject) => {
abort = args => reject(args) // 这一步很关键。CancelToken也是借鉴这种方式实现的“在他处cancel”。
// 在这里不执行reject方法,只是把它暴露出去,在合适的时机去执行p.abort()
})
p = Promise.race([p, rejectPromise]); // 依靠promise.race达到取消promise目的
p.abort = abort; // 把reject方法暴露给p.abort属性
return p
}
let p = wrap(new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 3000)
}))
p.then(res => {
console.log('res', res)
}, e => {
console.log('e,', e)
})
setTimeout(() => {
p.abort(123) // 如果上面的异步请求超过2秒,那么abort就会先执行,达到“取消”promise的目的
}, 2000)
理解了上面这个取消一个promise的代码,特别是把abort赋值给一个promise留待之后某个时机再调用,对于理解CancelToken会很有帮助。其中一个很关键的点就是:promise的成功还是失败,是你决定的。你未必要像常规写new Promise一样,紧接着就调用成功或失败方法。如果你不想立刻调用resolve或者reject,那就把resolve或者reject的方法暴露出去,到合适的时机去调用它。
源码包括注释一共50多行,有一点绕,(敲黑板提醒自己和小伙伴!!!)关键就在于处理怎样把resolve和reject暴露出去,做到之后某个时机做一个“取消的操作”,并让它影响现在正在执行的promise的状态:
function Cancel(message) {
this.message = message; // 可以传递因何取消请求的message
}
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
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); //关键代码二: 把接收到的“取消请求信息”token.reason传递给下一个then中成功函数作为参数
});
}
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c; // 关键代码三:实现“在他处取消请求”,与上面“终止一个promise”异曲同工。
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;
知道了上面的源码,我们在axios中如何取消已经发出的请求?比如切换页面的时候,原先也页面如果有还未接收回来的请求,其实可以不需要了。改造一下官方的get其请求来举例子:
var CancelToken = axios.CancelToken;
// 引入CancelToken,它是挂在axios实例上的一个构造函数
var {token, cancel} = CancelToken.source();
// CancelToken这个构造函数上有个静态方法source,调用它会得到一个对象,该对象包含token和cancel两个属性
axios.get('/get/server', {
cancelToken: token,
// 在发出请求时,config里需要配置一个属性cancelToken,它的值就是token。
// 这个token实际上是个promise。在aixos内部,发出请求的时候,会去执行该token的promise的then方法,但是它目前的状态还是pending,依靠cancel()方法才能把它变成成功态。
// then方法成功执行,即会接着调用XMLHttpRequest原型上的方法abort,达到取消http请求的目的。
})
.then(function (response) {
// ...
})
.catch(function (err) {
document.getElementById('people').innerHTML = '<li class="text-danger">' + err.message + '</li>';
});
cancel('Oops! Error happens! Look here!');
// axios取消请求的关键一步,执行了它,config配置的cancelToken才会生效,token的promise才会成功then。
对于上面“取消请求”代码,页面显示的内容就是cancel函数的参数,也是取消请求的传过去的信息:
推荐文章: 取消请求axios源码分析——取消请求 : "执行cancel是让token的promise变成成功"