一垮耳、如何設(shè)計(jì)一個(gè)取消請求的功能
取消請求是通過xhr.abort()這樣實(shí)現(xiàn)的。但是不可能將xhr變量暴露出來弃酌,因此在ajax請求的內(nèi)部必須有一個(gè)函數(shù)是取消請求的氨菇,通過這個(gè)函數(shù)取消請求達(dá)到隱藏xhr的目的儡炼。也可以直接是xhr.abort函數(shù),顯然這樣就無法對取消請求的過程無法控制了查蓉。
function cancelRequest(){
if(xhr==null){
return
}
xhr.abort()
reject("請求取消")
xhr = null
}
接下來乌询,這個(gè)函數(shù)怎么傳遞出去? 本來這個(gè)函數(shù)的控制權(quán)在封裝的ajax函數(shù)里面豌研,現(xiàn)在我們想把這個(gè)控制權(quán)轉(zhuǎn)交給調(diào)用者妹田。一種很巧妙的方式是通過函數(shù)傳遞出去,這要求初始化ajax請求時(shí)傳過來一個(gè)函數(shù)去接收cancelRequest函數(shù)鹃共。
初始化時(shí)
var cancel = null
rquest({
...
cancelToken:function(c){
cancel = c
}
...
})
封裝時(shí)
function rquest(config){
...
if(typeof config.cancelToken == "function"){
config.cancelToken(cancelRequest)
}
...
}
完整代碼如下:
function xhr(config){
return new Promise((resolve,reject)=>{
var {url,data,headers,method,cancelToken} = config
var xhr = new XMLHttpRequest();
xhr.onreadystatechange=function(){
if (xhr.readyState!=4){
return
}
if (xhr.status === 0) {
return;
}
if(xhr.status >= 200 && xhr.status < 300){
//TODO::
resolve(xhr.responseText)
}else{
reject("請求異常")
}
xhr = null
}
xhr.onabort = function(){
if(!xhr){return}
reject("請求取消");
xhr = null
}
xhr.onerror = function(){reject("請求出錯(cuò)");xhr = null}
xhr.ontimeout = function(){reject("請求超時(shí)");xhr = null}
function cancelRequest(message){
if(xhr==null){
return
}
xhr.abort()
reject(message)
xhr = null
}
if(typeof cancelToken == "function"){
cancelToken(cancelRequest)
}
xhr.open(method,url,true);
xhr.send(data);
})
}
這里有一點(diǎn)需要注意的是當(dāng)請求取消的時(shí)候鬼佣,status為0,而且首先觸發(fā)的onreadystatechange函數(shù)霜浴,為了不讓程序在這里reject晶衷,需要加上判斷if (xhr.status === 0)
∫趺希基本上取消請求的功能已經(jīng)可以工作了晌纫,但是這么做有一個(gè)缺點(diǎn),程序直接在onabort函數(shù)中reject出去了永丝,無法包含請求取消的原因锹漱,因?yàn)閤hr.abort()后面的reject無法工作。
二慕嚷、請求分發(fā)
請求分發(fā)過程對取消請求的實(shí)現(xiàn)極為重要哥牍,細(xì)節(jié)見axios流程分析1.6節(jié)。前面那樣實(shí)現(xiàn)的缺點(diǎn)
這個(gè)函數(shù)捕獲到異常之后會判斷是否是【請求取消】的異常喝检,如果不是會嘗試拋出【請求取消】的異常嗅辣。而axios為取消請求封裝的類中包含了這些功能。
三挠说、axios取消請求解析
3.1 取消信息的封裝辩诞,它包含可以識別出【取消請求】異常的屬性__CANCEL__
function Cancel(message) {
this.message = message;
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
3.2 取消異常類型判斷,對判斷取消異常的封裝
function isCancel(value) {
return !!(value && value.__CANCEL__);
};
3.3 取消請求核心邏輯
在最開始纺涤,我為了將http請求內(nèi)的abort函數(shù)傳遞出去,是給cancelToken賦予一個(gè)函數(shù)類型的值抠忘,并調(diào)用它撩炊,把cancel函數(shù)作為它的參數(shù)。理解取消請求核心邏輯需要注意兩點(diǎn):
- CancelToken怎么和config.cancelToken傳遞信息
- http請求內(nèi)部崎脉,abort函數(shù)怎么和CancelToken交互
因此這個(gè)部分需要做好和兩邊數(shù)據(jù)傳遞拧咳。
目前的情況是有三個(gè)函數(shù):cancel、executor囚灼、abort骆膝,我希望cancel調(diào)用之后立即調(diào)用abort祭衩,可以按照下面的寫法。在xhr函數(shù)中把真正能夠取消請求的abort函數(shù)傳遞給CancelToken存起來阅签,然后CancelToken初始化的時(shí)候把包裝abort函數(shù)的cancel函數(shù)作為參數(shù)傳遞給外部的函數(shù)掐暮。
function CancelToken(executor){
this.real_cancel = null
var token = this;
executor(function cancel(message) {
if (token.reason) {
return;
}
token.reason = new Cancel(message);
token.real_cancel && token.real_cancel()
});
}
//xhr函數(shù)中
function xhr(config){
//...
if (config.cancelToken) {
config.cancelToken.real_cancel = function(){
if (!request) {
return;
}
request.abort();
request = null;
}
}
//...
}
這樣做有一個(gè)缺陷,取消請求是異步進(jìn)行政钟,如果我想在取消請求之后立即做某件事是做不到的路克。所以axios是用promise來組織這三者之間的順序。
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);
});
}
//xhr 函數(shù)中
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
// reject(cancel);
// Clean up request
request = null;
});
}
首先把cancel函數(shù)傳遞給外部函數(shù)养交,只有cancel函數(shù)被調(diào)用了精算,CancelToken的promise才resolve,從而執(zhí)行then里面的方法碎连。在xhr函數(shù)中將取消請求的函數(shù)早就放到then里面去了灰羽,只是當(dāng)時(shí)不會執(zhí)行,等到cancel調(diào)用之后才執(zhí)行鱼辙。