面試官:如何中斷已發(fā)出去的請求?
面試者:(腦海里立馬產(chǎn)生一個疑惑:已經(jīng)發(fā)出去的請求還能取消掉?) 這個......這個......還真不知道。
面試完,馬上找度娘.....
推薦閱讀:axios解析之cancelToken取消請求原理[2]
AbortController
AbortController[3] 接口表示一個控制器對象,可以根據(jù)需要終止一個或多個Web請求。
AbortController():AbortController()構(gòu)造函數(shù)創(chuàng)建一個新的 AbortController 對象實(shí)例
signal:signal 屬性返回一個 AbortSignal 對象實(shí)例,它可以用來 with/about 一個Web(網(wǎng)絡(luò))請求
abort():終止一個尚未完成的Web(網(wǎng)絡(luò))請求,它能夠終止 fetch 請求,任何響應(yīng)Body的消費(fèi)者和流
Fetch 中斷請求
Fetch 是 Web 提供的一個用于獲取資源的接口,如果要終止 fetch 請求,則可以使用 Web 提供的 AbortController 接口。
首先我們使用 AbortController() 構(gòu)造函數(shù)創(chuàng)建一個控制器,然后使用 AbortController.signal 屬性獲取其關(guān)聯(lián) AbortSignal 對象的引用。當(dāng)一個 fetch request 初始化時,我們把 AbortSignal 作為一個選項(xiàng)傳遞到請求對象 (如下:{signal}) 。這將信號和控制器與獲取請求相關(guān)聯(lián),然后允許我們通過調(diào)用 AbortController.abort() 中止請求。
const?controller?=?new?AbortController();
let?signal?=?controller.signal;
?console.log('signal?的初始狀態(tài):?',?signal);
const?downloadBtn?=?document.querySelector('.download');
const?abortBtn?=?document.querySelector('.abort');
downloadBtn.addEventListener('click',?fetchVideo);
abortBtn.addEventListener('click',?function()?{
??controller.abort();
?console.log('signal?的中止?fàn)顟B(tài):?',?signal);
});
function?fetchVideo()?{
??//...
??fetch(url,?{signal}).then(function(response)?{
????//...
??}).catch(function(e)?{
????reports.textContent?=?'Download?error:?'?+?e.message;
??})
}
復(fù)制代碼
當(dāng)我們中止請求后,網(wǎng)絡(luò)請求變成了如下所示的情況:

我們再來看看 AbortSignal 中止前和中止后的狀態(tài):

可以看到,AbortSignal 對象的 aborted 屬性由初始時的 false 變成了中止后的 true 。
線上運(yùn)行示例[4] (代碼來源于MDN[5])
AbortControllter 有兼容性問題,如下:

axios 中斷請求
axions 中斷請求有兩種方式:
方式一
使用 CancelToken.souce 工廠方法創(chuàng)建一個 cancel token,代碼如下:
const?CancelToken?=?axios.CancelToken;
const?source?=?CancelToken.source();
axios.get('https://mdn.github.io/dom-examples/abort-api/sintel.mp4',?{
??cancelToken:?source.token
}).catch(function?(thrown)?{
??//?判斷請求是否已中止
??if?(axios.isCancel(thrown))?{
????//?參數(shù)?thrown?是自定義的信息
????console.log('Request?canceled',?thrown.message);
??}?else?{
????//?處理錯誤
??}
});
//?取消請求(message?參數(shù)是可選的)
source.cancel('Operation?canceled?by?the?user.');
復(fù)制代碼
中止后的網(wǎng)絡(luò)請求變成如下所示:

我們再來看看初始時和中止后的 souce 狀態(tài):

可以看到,初始時和中止后的 source 狀態(tài)并沒還有發(fā)生改變。那么我們是如何判斷請求的中止?fàn)顟B(tài)呢?axios 為我們提供了一個 isCancel() 方法,用于判斷請求的中止?fàn)顟B(tài)。isCancel() 方法的參數(shù),就是我們在中止請求時自定義的信息。

方式二
通過傳遞一個 executor 函數(shù)到 CancelToken 的構(gòu)造函數(shù)來創(chuàng)建一個 cancel token:
const?CancelToken?=?axios.CancelToken;
let?cancel;
axios.get('/user/12345',?{
??cancelToken:?new?CancelToken(function?executor(c)?{
????//?executor?函數(shù)接收一個?cancel?函數(shù)作為參數(shù)
????cancel?=?c;
??})
});
//?取消請求
cancel('Operation?canceled?by?the?user.');
復(fù)制代碼
瀏覽器運(yùn)行結(jié)果與方式一一致,此處不再贅述。
線上運(yùn)行示例[6] (代碼來源于MDN[7])
umi-request 中斷請求
umi-request 基于 fetch 封裝, 兼具 fetch 與 axios 的特點(diǎn), 中止請求與 fetch 和 axios 一致不再過多贅述,詳情可見官方文檔 中止請求[8]
需要注意的是 AbortController 在低版本瀏覽器polyfill有問題,umi-request 在某些版本中并未提供 AbortController 的方式中止請求。
umi 項(xiàng)目中使用 CancelToken 中止請求
umi 項(xiàng)目中默認(rèn)的請求庫是umi-request,因此我們可以使用umi-request提供的方法來中止請求。另外,在umi項(xiàng)目中可以搭配使用了dva,因此下面簡單介紹下在dva中使用CancelToken中止請求的流程。
1、在 services 目錄下的文件中編寫請求函數(shù)和取消請求的函數(shù)
import?request?from?'@/utils/request';
const?CancelToken?=?request.CancelToken;
let?cancel:?any;
//?合同文件上傳?OSS
export?async?function?uploadContractFileToOSS(postBody:?Blob):?Promise?{
??return?request(`/fms/ossUpload/financial_sys/contractFile`,?{
????method:?"POST",
????data:?postBody,
????requestType:?'form',
????//?傳遞一個?executor?函數(shù)到?CancelToken?的構(gòu)造函數(shù)來創(chuàng)建一個?cancel?token
????cancelToken:?new?CancelToken((c)?=>?{
??????cancel?=?c
????})
??})
}
//?取消合同文件上傳
export?async?function?cancelUploadFile()?{
??return?cancel?&&?cancel()
}
復(fù)制代碼
2、在 models 中編寫 Effect:
*uploadContractFileToOSS({?payload?}:?AnyAction,?{?call,?put?}:?EffectsCommandMap):?any?{
??const?response?=?yield?call(uploadContractFileToOSS,?payload);
??yield?put({
????type:?'save',
????payload:?{
??????uploadOSSResult:?response?.data,
????}
??})
??return?response?.data
},
*cancelUploadFile(_:?AnyAction,?{?call?}:?EffectsCommandMap):?any?{
??const?response?=?yield?call(cancelUploadFile)
??return?response
},
復(fù)制代碼
3、在頁面中通過dispatch函數(shù)觸發(fā)相應(yīng)的action:
//?發(fā)起請求
dispatch({
??type:?'contract/fetchContractFiles',
??payload:?{
????contractId:?`${id}`,
??}
})
//?取消請求
dispatch({
??type:?"contract/cancelUploadFile"
})
???
復(fù)制代碼
4、在 utils/request.js 中統(tǒng)一處理中止請求的攔截:
const?errorHandler?=?(error:?{?response:?Response?}):?Response?=>?{
??const?{?response?}?=?error;
??notification.destroy()
??if?(response?&&?response.status)?{
????const?errorText?=?codeMessage[response.status]?||?response.statusText;
????const?{?status,?url?}?=?response;
????notification.error({
??????message:?`請求錯誤?${status}:?${url}`,
??????description:?errorText,
????});
??}?else?if?(error?.['type']?===?'TypeError')?{
????notification.error({
??????description:?'您的網(wǎng)絡(luò)發(fā)生異常,無法連接服務(wù)器',
??????message:?'網(wǎng)絡(luò)異常',
????});
??}?else?if?(error?.['request']?.['options']?.['cancelToken'])?{
????notification.warn({
??????description:?'當(dāng)前請求已被取消',
??????message:?'取消請求',
????});
??}?else?if?(!response)?{
????notification.error({
??????description:?'您的網(wǎng)絡(luò)發(fā)生異常,無法連接服務(wù)器',
??????message:?'網(wǎng)絡(luò)異常',
????});
??}?else?{
????notification.error({
??????description:?'請聯(lián)系網(wǎng)站開發(fā)人員處理',
??????message:?'未知錯誤',
????});
??}
??return?response;
};
復(fù)制代碼
關(guān)于本文
作者:紫圣
https://juejin.cn/post/7033906910583586829
