面试官:如何中断已发出去的请求?

开发 前端
Fetch 是 Web 提供的一个用于获取资源的接口,如果要终止 fetch 请求,则可以使用 Web 提供的 AbortController 接口。

 面试官:请求已经发出去了,如何取消掉这个已经发出去的请求?

面试者:(脑海里立马产生一个疑惑:已经发出去的请求还能取消掉?) 这个......这个......还真不知道。

面试完,马上找度娘.....

推荐阅读:axios解析之cancelToken取消请求原理[2]

AbortController

AbortController[3] 接口表示一个控制器对象,可以根据需要终止一个或多个Web请求。

  •  AbortController():AbortController()构造函数创建一个新的 AbortController 对象实例
  •  signal:signal 属性返回一个 AbortSignal 对象实例,它可以用来 with/about 一个Web(网络)请求
  •  abort():终止一个尚未完成的Web(网络)请求,它能够终止 fetch 请求,任何响应Body的消费者和流

Fetch 中断请求

Fetch 是 Web 提供的一个用于获取资源的接口,如果要终止 fetch 请求,则可以使用 Web 提供的 AbortController 接口。

首先我们使用 AbortController() 构造函数创建一个控制器,然后使用 AbortController.signal 属性获取其关联 AbortSignal 对象的引用。当一个 fetch request 初始化时,我们把 AbortSignal 作为一个选项传递到请求对象 (如下:{signal}) 。这将信号和控制器与获取请求相关联,然后允许我们通过调用 AbortController.abort() 中止请求。 

  1. const controller = new AbortController();  
  2. let signal = controller.signal;  
  3.  console.log('signal 的初始状态: ', signal);  
  4. const downloadBtn = document.querySelector('.download');  
  5. const abortBtn = document.querySelector('.abort');  
  6. downloadBtn.addEventListener('click', fetchVideo);  
  7. abortBtn.addEventListener('click', function() {  
  8.   controller.abort();  
  9.  console.log('signal 的中止状态: ', signal);  
  10. });  
  11. function fetchVideo() {  
  12.   //...  
  13.   fetch(url, {signal}).then(function(response) {  
  14.     //...  
  15.   }).catch(function(e) {  
  16.     reports.textContent = 'Download error: ' + e.message;  
  17.   })  
  18.  
  19. 复制代码 

当我们中止请求后,网络请求变成了如下所示的情况:

我们再来看看 AbortSignal 中止前和中止后的状态:

可以看到,AbortSignal 对象的 aborted 属性由初始时的 false 变成了中止后的 true 。

线上运行示例[4] (代码来源于MDN[5])

AbortControllter 有兼容性问题,如下:

axios 中断请求

axions 中断请求有两种方式:

方式一

使用 CancelToken.souce 工厂方法创建一个 cancel token,代码如下: 

  1. const CancelToken = axios.CancelToken;  
  2. const source = CancelToken.source();  
  3. axios.get('https://mdn.github.io/dom-examples/abort-api/sintel.mp4', {  
  4.   cancelToken: source.token  
  5. }).catch(function (thrown) {  
  6.   // 判断请求是否已中止  
  7.   if (axios.isCancel(thrown)) {  
  8.     // 参数 thrown 是自定义的信息  
  9.     console.log('Request canceled', thrown.message);  
  10.   } else {  
  11.     // 处理错误  
  12.   }  
  13. });  
  14. // 取消请求(message 参数是可选的)  
  15. source.cancel('Operation canceled by the user.');  
  16. 复制代码 

中止后的网络请求变成如下所示:

我们再来看看初始时和中止后的 souce 状态:

可以看到,初始时和中止后的 source 状态并没还有发生改变。那么我们是如何判断请求的中止状态呢?axios 为我们提供了一个 isCancel() 方法,用于判断请求的中止状态。isCancel() 方法的参数,就是我们在中止请求时自定义的信息。

方式二

通过传递一个 executor 函数到 CancelToken 的构造函数来创建一个 cancel token: 

  1. const CancelToken = axios.CancelToken;  
  2. let cancel;  
  3. axios.get('/user/12345', { 
  4.   cancelToken: new CancelToken(function executor(c) {  
  5.     // executor 函数接收一个 cancel 函数作为参数  
  6.     ccancel = c;  
  7.   })  
  8. });  
  9. // 取消请求  
  10. cancel('Operation canceled by the user.');  
  11. 复制代码 

浏览器运行结果与方式一一致,此处不再赘述。

线上运行示例[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 目录下的文件中编写请求函数和取消请求的函数 

  1. import request from '@/utils/request';  
  2. const CancelToken = request.CancelToken;  
  3. let cancel: any;  
  4. // 合同文件上传 OSS  
  5. export async function uploadContractFileToOSS(postBody: Blob): Promise<any> {  
  6.   return request(`/fms/ossUpload/financial_sys/contractFile`, {  
  7.     method: "POST",  
  8.     data: postBody,  
  9.     requestType: 'form',  
  10.     // 传递一个 executor 函数到 CancelToken 的构造函数来创建一个 cancel token  
  11.     cancelToken: new CancelToken((c) => {  
  12.       ccancel = c  
  13.     })  
  14.   })  
  15.  
  16. // 取消合同文件上传  
  17. export async function cancelUploadFile() {  
  18.   return cancel && cancel()  
  19.  
  20. 复制代码 

2、在 models 中编写 Effect: 

  1. *uploadContractFileToOSS({ payload }: AnyAction, { call, put }: EffectsCommandMap): any {  
  2.   const response = yield call(uploadContractFileToOSS, payload);  
  3.   yield put({  
  4.     type: 'save',  
  5.     payload: {  
  6.       uploadOSSResult: response?.data,  
  7.     }  
  8.   })  
  9.   return response?.data  
  10. },  
  11. *cancelUploadFile(_: AnyAction, { call }: EffectsCommandMap): any {  
  12.   const response = yield call(cancelUploadFile)  
  13.   return response  
  14. },  
  15. 复制代码 

3、在页面中通过dispatch函数触发相应的action: 

  1. // 发起请求  
  2. dispatch({  
  3.   type: 'contract/fetchContractFiles',  
  4.   payload: {  
  5.     contractId: `${id}`,  
  6.   }  
  7. })  
  8. // 取消请求  
  9. dispatch({  
  10.   type: "contract/cancelUploadFile"  
  11. })    
  12. 复制代码 

4、在 utils/request.js 中统一处理中止请求的拦截: 

  1. const errorHandler = (error: { response: Response }): Response => {  
  2.   const { response } = error;  
  3.   notification.destroy()  
  4.   if (response && response.status) {  
  5.     const errorText = codeMessage[response.status] || response.statusText;  
  6.     const { status, url } = response;  
  7.     notification.error({  
  8.       message: `请求错误 ${status}: ${url}`,  
  9.       description: errorText,  
  10.     });  
  11.   } else if (error?.['type'] === 'TypeError') {  
  12.     notification.error({  
  13.       description: '您的网络发生异常,无法连接服务器',  
  14.       message: '网络异常',  
  15.     });  
  16.   } else if (error?.['request']?.['options']?.['cancelToken']) {  
  17.     notification.warn({  
  18.       description: '当前请求已被取消',  
  19.       message: '取消请求',  
  20.     });  
  21.   } else if (!response) {  
  22.     notification.error({  
  23.       description: '您的网络发生异常,无法连接服务器',  
  24.       message: '网络异常',  
  25.     });  
  26.   } else {  
  27.     notification.error({  
  28.       description: '请联系网站开发人员处理', 
  29.       message: '未知错误',  
  30.     });  
  31.   }  
  32.   return response;  
  33. };  
  34. 复制代码  

 

责任编辑:庞桂玉 来源: Web开发
相关推荐

2022-04-01 12:38:32

cookie代码面试

2015-08-13 10:29:12

面试面试官

2021-01-18 05:13:04

TomcatHttp

2010-08-12 16:28:35

面试官

2020-06-12 15:50:56

options前端服务器

2021-07-06 07:08:18

管控数据数仓

2021-05-27 05:37:10

HTTP请求头浏览器

2015-08-24 09:00:36

面试面试官

2010-08-27 10:53:14

面试

2022-04-08 08:26:03

JavaHTTP请求

2021-05-18 08:32:33

TCPIP协议

2022-05-23 08:43:02

BigIntJavaScript内置对象

2021-10-26 10:29:45

扫码登录功能

2021-05-20 08:34:03

CDN原理网络

2021-05-19 06:07:21

CSS 斜线效果技巧

2021-05-18 08:47:30

面试团队考核

2021-12-15 06:58:13

List 集合LinkedHashS

2021-10-22 08:37:13

消息不丢失rocketmq消息队列

2021-05-20 08:54:16

Go面向对象

2021-04-21 09:28:17

字节面试官SetTimeout
点赞
收藏

51CTO技术栈公众号