如何封装一个可取消的 HTTP 请求?

news/2024/11/2 17:25:05/
http://www.w3.org/2000/svg" style="display: none;">

前言

你可能会好奇什么样的场景会需要取消 HTTP 请求呢?

确实在实际的项目开发中,可能会很少有这样的需求,但是不代表没有,比如:
https://img-blog.csdnimg.cn/img_convert/48866d3631b5bb2d927e44212b373899.png" alt="" />

假如要实现上述这个公告栏,每点击一个 tab 按钮就会切换展示容器容器中的内容,但是由于这是三个 tab 按钮对应展示容器和信息条目结构样式都一致,于是为了 复用这个结构,将展示容器和其中的信息条目都作为三个 tab 按钮对应展示效果,只是点击每个 tab 按钮后 发送请求 后得到的数据不同.

正常情况下 tab1 对应的数据在初始化时进行展示,但是现在还存在一个问题,那就是如果点击完 tab2 并且已经发送了请求获取数据,假设这个请求需要 30s 后才响应回来,由于用户比较着急于是直接点了 tab3 ,此时 tab3 对应的接口已经发送出去但是未响应,而此时 tab2 对应的接口响应回来了,就会导致展示容器中的信息条目展现了错误的数据.

现在你应该知道,为什么需要一个可取消的 HTTP 请求了吧!!

准备工作

为了避免 端口、协议、IP 等不一致产生的跨域问题,这里就直接使用 express 启动一个本地服务,并返回测试页面 index.html,目录结构如下:

https://img-blog.csdnimg.cn/img_convert/55963f2167c0823c4b7e24a6e052cebf.png" alt="image.png" />

  • 其中最外层的 serverv.js 就是本地服务的代码,内容很简单:
    const path = require('path')
    const express = require('express')
    const app = express()// 返回静态资源
    app.use(express.static(path.join(__dirname, 'src')))// 处理 api 请求接口
    app.get('/message', function (req, res) {setTimeout(()=>{res.send('Hello World')} , 1000);
    });app.listen(3000, (error) => {if (error) {console.error("server error:", error);return}console.log("server runing at port 3000 ...");
    })
    
  • src/index.html 是测试页面,里面引入 src/request.js 中封装的请求方法
  • src/request.js 简单的封装了一些请求方法,包含 XMLHttpRequestfetchaxios 三种方式,内容如下:
    // xhrRequest
    function xhrRequest({method = 'get',url,params = null,async = true,success,
    }) {success = typeof success === 'function' ? success : () => {}const xhr = new XMLHttpRequest()xhr.open(method, url, async)xhr.onreadystatechange = () => {if (xhr.readyState == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {success(xhr.responseText)} else {console.log('Request was unsuccessful: ' + xhr.status)}}}method === 'post' ? xhr.send(params || null) : xhr.send()return () => {console.log(`当前请求已被取消,url:`, url)xhr.abort()}
    }// fetchRequest
    function fetchRequest({ method = 'GET', url, params }) {let abortController = new AbortController()let patload = {method: method,signal: abortController.signal,}if (method === 'POST') patload.body = JSON.stringify(params)return {abort: () => {console.log(`当前请求已被取消,url:`, url)abortController.abort()},result: fetch(url, patload),}
    }// axiosRequest
    window.axiosCancelArr = []
    let Axios = null
    function axiosRequest() {if (Axios) return AxiosAxios = axios.create({baseURL: '',timeout: 10000,})//请求前拦截Axios.interceptors.request.use((config) => {// 添加取消标记config.cancelToken = new axios.CancelToken((cancel) => {window.axiosCancelArr.push({url: config.url, cancel})})return config},(error) => {return Promise.reject(error)},)//请求后返回数据拦截Axios.interceptors.response.use((res) => {return res},(res) => {return Promise.reject(res)},)return Axios
    }
    

原生方法取消请求

XMLHttpRequest —— (new XMLHttpRequest()).abort()

下面的图示,演示了 请求成功请求被取消 时的两种表现:
https://img-blog.csdnimg.cn/img_convert/f293b4bce37d125fbad4c8b2e6a885b1.gif" alt="" />

以下是在测试页面 index.html 中的代码,对应 js 代码在准备工作部分:

  <div><button onclick="send()">send request</button><button onclick="cancel()">cancel request</button></div><script src="./request.js"></script><script>let cancel = () => {}function send(){console.log("正在发送 xhr 请求...")// 获取取消请求的方法cancel = xhrRequest({url:'/message',success(data){console.log("接收数据:", data)}})}<script>

fetch —— (new AbortController()).abort()

下面的图示,演示了 请求成功请求被取消 时的两种表现:

https://img-blog.csdnimg.cn/img_convert/8bf8725785096cbbae236748c6247f68.gif" alt="" />

以下是在测试页面 index.html 中的代码,对应 js 代码在准备工作部分:

<div><button onclick="send()">send request</button><button onclick="cancel()">cancel request</button>
</div><script src="./request.js"></script>
<script>let cancel = () => {}// fetchfunction send() {console.log('正在发送 fetch 请求...')const { result, abort } = fetchRequest({url: '/message',})cancel = abortresult.then((data) => {console.log('接收数据:', data)}).catch((error) => {console.log('fetch error:', error)})}</script>

axios —— new axios.CancelToken((cancel) => {}))

下面的图示,演示了 请求成功请求被取消 时的两种表现:

https://img-blog.csdnimg.cn/img_convert/2f6074b24594bf0f6814a64286cc922b.gif" alt="" />

以下是在测试页面 index.html 中的代码,对应 js 代码在准备工作部分:

<div><button onclick="send()">send request</button><button onclick="cancel()">cancel request</button>
</div><script src="./request.js"></script>
<script>let cancel = () => {}// axiosconst AxiosInstance = axiosRequest()function send() {console.log('正在发 axios 送请求...')AxiosInstance.get('/message').then((res) => {console.log('接收数据:', res)}).catch((error) => {console.log('axios error:', error)})}cancel = () => {console.log('axiosCancel = ', window.axiosCancel)window.axiosCancelArr.forEach((item) => {item.cancel()})}
</script>

实现自定义方法 " 取消请求 "

注意这里的 “请求方法” 是包含引号的,也就是说并不是像原生方法那样真的把已发送出去的请求进行取消,但是可以通过其他方式不在接收之前请求响应的数据,转而使用最新接口响应的数据,从侧面实现避免旧接口响应造成的干扰.

如果要自定义实现 “取消请求” 方法,最先想到的应该是 promise,因为 promise 有一个重要的特性就是:状态一旦从 pending -> fullfilled 或 pending -> rejected 的变更,之后就无法在进行更改.

既然如此,我们就可以在接口响应时通过 promise.resolve() 进行返回,一旦需要 “取消请求” 就可以先于接口响应时先更改对应 promise 的状态,从而抛弃上一次接口响应的数据.

https://img-blog.csdnimg.cn/img_convert/eccf7903fa1184564e7f488a620e307c.gif" alt="" />

request.js 中代码如下:

// xhrRequestPromise
window.cancelXhrRequestArr = []
function xhrRequestPromise({method = 'get',url,params = null,async = true,
}) {return new Promise((resolve, reject) => {cancelXhrRequestArr.push({ reject, url })success = typeof success === 'function' ? success : () => {}const xhr = new XMLHttpRequest()xhr.open(method, url, async)xhr.onreadystatechange = () => {if (xhr.readyState == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {resolve(xhr.responseText)} else {console.log('Request was unsuccessful: ' + xhr.status)}}}method === 'post' ? xhr.send(params || null) : xhr.send()})
}

测试页面 index.html 代码如下:

<div><button onclick="send()">send request</button><button onclick="cancel()">cancel request</button>
</div><script src="./request.js"></script>
<script>let cancel = () => {cancelXhrRequestArr.forEach(({reject, url}) => {reject(`abandon request url:${url}`)})}// xhrRequestPromisefunction send() {console.log('正在发送 xhr 请求...')const xhrp = xhrRequestPromise({ url: '/message' }).then((res) => {console.log('接收数据:', res)}).catch((error) => {console.log('xhrRequest error:', error)})}</script>

最后

上面通过 XMLHttpRequestfetchaxios 三种方式来演示了如何取消请求,同时也通过 promise 实现了自定义 “取消请求” 的方法,简单来说,通过原生方法就是把这个请求给取消了,自定义实现方法就是把将旧数据直接抛弃不适用,明白了这一点其实通过其他的方式实现也可以.


http://www.ppmy.cn/news/1543925.html

相关文章

Java 集合一口气讲完!(中)d=====( ̄▽ ̄*)b

Java 队列 Java集合教程 - Java队列 队列是只能在其上执行操作的对象的集合两端的队列。 队列有两个末端&#xff0c;称为头和尾。 在简单队列中&#xff0c;对象被添加到尾部并从头部删除并首先删除首先添加的对象。 Java Collections Framework支持以下类型的队列。 简单…

9.4 PIN definitions

9.4 PIN定义 9.4.0 引言 第9.4节定义了UICC上应存在的PIN类型&#xff0c;即通用PIN和应用程序PIN&#xff0c;以及UICC/应用程序所需的其他类型的访问条件。 9.4.1 通用PIN 通用PIN是在多应用环境中使用的PIN&#xff0c;允许多个应用程序共享一个公共PIN。通用PIN是一个全局…

RabbitMQ的解耦、异步、削峰是什么?

RabbitMQ在分布式系统和微服务架构中起到了重要的作用&#xff0c;其特性可以实现解耦、异步以及削峰&#xff0c;下面是对这三个概念的详细解释&#xff1a; 1. 解耦 解耦是指使系统的不同组件间的依赖关系减少或消失。在使用RabbitMQ时&#xff0c;生产者&#xff08;发送消…

【Java】设计模式——单例设计模式

1.什么是设计模式 设计模式是一种被广泛认可的、可复用的解决方案&#xff0c;用于在软件开发中解决常见的问题。它们是对软件设计中常见问题的总结与提炼&#xff0c;提供了一套可遵循的标准和最佳实践&#xff0c;帮助开发人员构建高效、可维护和灵活的系统。 2.设计模式的分…

计算机网络八股文个人总结

1.TCP/IP模型和OSI模型的区别 在计算机网络中&#xff0c;TCP/IP 模型和 OSI 模型是两个重要的网络协议模型。它们帮助我们理解计算机通信的工作原理。以下是它们的主要区别&#xff0c;以通俗易懂的方式进行解释&#xff1a; 1. 模型层数 OSI 模型&#xff1a;有 7 层&#…

【Spring】Spring Boot 配置文件(7)

本系列共涉及4个框架&#xff1a;Sping,SpringBoot,Spring MVC,Mybatis。 博客涉及框架的重要知识点&#xff0c;根据序号学习即可。 有什么不懂的都可以问我&#xff0c;看到消息会回复的&#xff0c;可能会不及时&#xff0c;请见谅&#xff01;&#xff01; 目录 本系列共…

vscode markdown-image 图片粘贴自动上传到本地目录设置

.vscode/settings.json文件内容 {"markdown-image.base.fileNameFormat": "${hash}-${YY}${MM}${DD}-${HH}${mm}${ss}","markdown-image.local.path": "./images","markdown-image.base.uploadMethod": "Local",…

做等保二级备案需要准备哪些材料

做等保二级备案需要准备的材料主要包括以下几类&#xff1a; 1. 基本信息材料 营业执照副本&#xff1a;证明企业的合法经营资格。法人身份证明&#xff1a;证明企业法定代表人的身份。系统基本信息情况介绍表&#xff1a;详细描述信息系统的功能、应用场景、安全需求等。 2…