面試官:如何中斷已發(fā)出去的請求?
面試官:請求已經發(fā)出去了,如何取消掉這個已經發(fā)出去的請求?
面試者:(腦海里立馬產生一個疑惑:已經發(fā)出去的請求還能取消掉?) 這個......這個......還真不知道。
面試完,馬上找度娘.....
推薦閱讀:axios解析之cancelToken取消請求原理[2]
AbortController
AbortController[3] 接口表示一個控制器對象,可以根據需要終止一個或多個Web請求。
- AbortController():AbortController()構造函數創(chuàng)建一個新的 AbortController 對象實例
- signal:signal 屬性返回一個 AbortSignal 對象實例,它可以用來 with/about 一個Web(網絡)請求
- abort():終止一個尚未完成的Web(網絡)請求,它能夠終止 fetch 請求,任何響應Body的消費者和流
Fetch 中斷請求
Fetch 是 Web 提供的一個用于獲取資源的接口,如果要終止 fetch 請求,則可以使用 Web 提供的 AbortController 接口。
首先我們使用 AbortController() 構造函數創(chuàng)建一個控制器,然后使用 AbortController.signal 屬性獲取其關聯 AbortSignal 對象的引用。當一個 fetch request 初始化時,我們把 AbortSignal 作為一個選項傳遞到請求對象 (如下:{signal}) 。這將信號和控制器與獲取請求相關聯,然后允許我們通過調用 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 的中止狀態(tài): ', signal);
- });
- function fetchVideo() {
- //...
- fetch(url, {signal}).then(function(response) {
- //...
- }).catch(function(e) {
- reports.textContent = 'Download error: ' + e.message;
- })
- }
- 復制代碼
當我們中止請求后,網絡請求變成了如下所示的情況:
我們再來看看 AbortSignal 中止前和中止后的狀態(tài):
可以看到,AbortSignal 對象的 aborted 屬性由初始時的 false 變成了中止后的 true 。
線上運行示例[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)) {
- // 參數 thrown 是自定義的信息
- console.log('Request canceled', thrown.message);
- } else {
- // 處理錯誤
- }
- });
- // 取消請求(message 參數是可選的)
- source.cancel('Operation canceled by the user.');
- 復制代碼
中止后的網絡請求變成如下所示:
我們再來看看初始時和中止后的 souce 狀態(tài):
可以看到,初始時和中止后的 source 狀態(tài)并沒還有發(fā)生改變。那么我們是如何判斷請求的中止狀態(tài)呢?axios 為我們提供了一個 isCancel() 方法,用于判斷請求的中止狀態(tài)。isCancel() 方法的參數,就是我們在中止請求時自定義的信息。
方式二
通過傳遞一個 executor 函數到 CancelToken 的構造函數來創(chuàng)建一個 cancel token:
- const CancelToken = axios.CancelToken;
- let cancel;
- axios.get('/user/12345', {
- cancelToken: new CancelToken(function executor(c) {
- // executor 函數接收一個 cancel 函數作為參數
- ccancel = c;
- })
- });
- // 取消請求
- cancel('Operation canceled by the user.');
- 復制代碼
瀏覽器運行結果與方式一一致,此處不再贅述。
線上運行示例[6] (代碼來源于MDN[7])
umi-request 中斷請求
umi-request 基于 fetch 封裝, 兼具 fetch 與 axios 的特點, 中止請求與 fetch 和 axios 一致不再過多贅述,詳情可見官方文檔 中止請求[8]
需要注意的是 AbortController 在低版本瀏覽器polyfill有問題,umi-request 在某些版本中并未提供 AbortController 的方式中止請求。
umi 項目中使用 CancelToken 中止請求
umi 項目中默認的請求庫是umi-request,因此我們可以使用umi-request提供的方法來中止請求。另外,在umi項目中可以搭配使用了dva,因此下面簡單介紹下在dva中使用CancelToken中止請求的流程。
1、在 services 目錄下的文件中編寫請求函數和取消請求的函數
- import request from '@/utils/request';
- const CancelToken = request.CancelToken;
- let cancel: any;
- // 合同文件上傳 OSS
- export async function uploadContractFileToOSS(postBody: Blob): Promise<any> {
- return request(`/fms/ossUpload/financial_sys/contractFile`, {
- method: "POST",
- data: postBody,
- requestType: 'form',
- // 傳遞一個 executor 函數到 CancelToken 的構造函數來創(chuàng)建一個 cancel token
- cancelToken: new CancelToken((c) => {
- ccancel = c
- })
- })
- }
- // 取消合同文件上傳
- export async function cancelUploadFile() {
- return cancel && cancel()
- }
- 復制代碼
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
- },
- 復制代碼
3、在頁面中通過dispatch函數觸發(fā)相應的action:
- // 發(fā)起請求
- dispatch({
- type: 'contract/fetchContractFiles',
- payload: {
- contractId: `${id}`,
- }
- })
- // 取消請求
- dispatch({
- type: "contract/cancelUploadFile"
- })
- 復制代碼
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: '您的網絡發(fā)生異常,無法連接服務器',
- message: '網絡異常',
- });
- } else if (error?.['request']?.['options']?.['cancelToken']) {
- notification.warn({
- description: '當前請求已被取消',
- message: '取消請求',
- });
- } else if (!response) {
- notification.error({
- description: '您的網絡發(fā)生異常,無法連接服務器',
- message: '網絡異常',
- });
- } else {
- notification.error({
- description: '請聯系網站開發(fā)人員處理',
- message: '未知錯誤',
- });
- }
- return response;
- };
- 復制代碼