使用axios阻止多余的请求
前言
在日常的复杂业务中,我们总是调用大量的后端api。为了提升服务器的效率,我们需要尽可能的减少每个用户的平均请求数量。
本文的内容,着重对重复和多余的请求做处理,以节约网络资源。比如:
- 用户高频点击某个发送请求的按钮
- 本地的定时请求任务,前一个请求还未完成就发送了新的请求
第二个场景比较罕见,不过在一次开发任务中,糟糕的测试服务器使这个问题严重影响了我的开发体验,印象深刻。
在服务器资源充足的时候,上述问题一般不会暴露,不过在服务器资源紧张的时候,减少非必要请求可以为服务器争取更多的可用空间。
本文会展示“阻止多余请求”的工具的实现,最后,会稍微探讨我们通过这个工具还可以做些什么。
做什么
如何定义“重复”和“多余”?当我们通过请求获取数据时,我们希望在该请求完成前,不去发送新的相同请求;当我们提交数据时,我们希望提交的数据是最新的,所以需要取消之前的请求。我们把问题展开,发现需求是这样的:
- 当请求方法为
get
时,当x
请求未完成时,阻止新触发的 x 请求 - 当请求方法不为
get
时,当x
请求未完成时,如果有新的x
请求被触发,则取消前一个x
请求,重新发送新的x
请求,以保证发送的数据是最新的 - 可以手动清除特定的请求
怎么做
使用工具
我们将会用到 axios
库中的 CancelToken
API,这个API专门用于取消某个正在执行的请求,具体参加文档,不再赘述。文档传送门
整体思路
先规定这个工具应该放在哪里。作为一个全局的中间件,放在axios的拦截器里是再合适不过了。我们定义两个方法,cancelReq
和 resetReq
。
cancelReq
负责标记正在进行的请求、判断是否有相同的请求正在执行;resetReq
负责清除已完成请求的标记。
import { cancelReq, resetReq } from 'cancel-token.js'
// axios.js
// 请求拦截器
_axios.interceptors.request.use(
function(config) {
// * cancelToken 阻止重复请求
config = cancelReq(config)
return config
},
function(error) {
return Promise.reject(error)
}
)
// 响应拦截器
_axios.interceptors.response.use(
function(response) {
// * cancelToken 完成请求,清除标记
resetReq(response.config)
},
function(error) {
if (error.config) {
resetReq(error.config)
}
return Promise.reject(error)
}
)
我们将这个工具放在 cancel-token.js
文件中。首先我们需要一个存放“正在执行的请求”的一个队列 reqList
;同时,我们规定一个构造函数 Req
,存放一个请求的关键信息,Req
主要存储一个请求的 url
、 method
以及 用于结束该请求的cancel
方法。
考虑到某些复杂的业务场景,如果上述参数不足以区分每个请求(比如某个页面需要同时发送两次 /get/method
的 get
请求,分别带上 ?query=a
和 ?param=b
),这时,我们需要在 Req
构造函数中添加 query
或 data
。
// cancel-token.js
// * 存放当前正在执行的请求
const reqList = []
/**
* * 构造函数 - axios请求的关键对象(url/method/params/data)
* @param {*} config 当前axios请求的config
*/
function Req(config) {
this.url = config.url
this.method = config.method
this.cancel = config.cancel
// * 可能需要添加config.query或config.data
}
cancelReq
方法的实现
cancelReq
需要做的是:
- 将新的请求添加到“正在执行”的队列中
- 阻止重复的
get
请求 - 如果有新的提交数据的请求,如
post
、put
,取消之前的那个请求并清除出队列,执行新的请求并添加到队列
cancelReq
使用到了 CancelToken
。我们使用 _cancel
变量去接受 cancel
方法,并将这个方法添加到该请求的 config
,这样我们可以随时随地调用 cancel('这个请求结束了')
终止这个请求。
准备完成后,创建该请求的 Req
实例 _req
,这个 _req
将存放于执行队列 reqList
中,如果队列里存在该请求,则按照下方 else {}
函数体中的逻辑执行。
// cancel-token.js
import { CancelToken } from 'axios'
// * 阻止请求重复发送
export function cancelReq(config) {
const _config = config
let _cancel
// * 拿到cancel方法
_config.cancelToken = new CancelToken(function(cancel) {
// 赋值
_cancel = cancel
_config.cancel = cancel
})
// 创建Req的一个实例
const _req = new Req(_config)
// ?注意,findReq 这个方法用于查找这个req实例是否已经存在于执行队列reqList中
const _index = findReq(_req)
if (_index === -1) {
// 该请求未处于执行状态,将它添加到执行队列,over
reqList.push(_req)
} else {
// 看来这个请求正在执行
if (_req.method.toLowerCase() === 'get') {
// * 如果method 为 get 则阻止后一个请求
_cancel(`已取消重复发送的请求: ${_config.url}`)
} else {
// * 如果不为get 则取消前一个请求,发送新的请求
reqList[_index].cancel(`已取消重复发送的请求: ${_config.url}`)
reqList.splice(_index, 1)
}
}
// 最后返回处理完成的config,交给axios请求拦截器
return _config
}
我们判断 reqList
中是否有某个 req
,使用的是一个 findReq
方法。下面看一下 findReq
方法的实现。
/**
* * 将要发送的请求是否已经存在
* @param {*} target 将要进行的请求
* @returns {*} 这个请求的index,-1即没有
*/
function findReq(target) {
let _index = -1
for (const i in reqList) {
if (reqList[i].url === target.url &&
reqList[i].method === target.method) {
// * 还可以添加其他的规则
_index = i
break
}
}
return _index
}
resetReq
方法的实现
resetReq
需要做的是:
- 清除完成的请求
做法与 cancelReq
类似,创建一个 Req
实例,在 reqList
中查找该请求并清除。
// * 清除完成发送的请求
export function resetReq(config) {
const _req = new Req(config)
const _index = findReq(_req)
if (_index !== -1) {
reqList.splice(_index, 1)
}
}
手动清除请求的方法
某些具体的业务的实现可能会需要我们手动清理某些需求,此时我们定义一个 manualCancelReq
方法。这个方法清理掉特定 url
和 method
的请求,并且可以做到清理不至一个匹配到的请求(如果有的话)。这里使用了递归来做到清理多个请求。
import { findIndex as _findIndex } from 'lodash'
/**
* * 手动清除某个或某些可能存在的请求(根据url匹配)
* @param {string} url 请求的url
* @param {string | null} method 请求的method,可传 null
* @param {boolean} multi 清除多个请求的就传 true
*/
export function manualCancelReq(url, method, multi) {
const _reqIndex = _findIndex(
reqList,
Object.assign({ url }, method ? { method } : {})
)
if (_reqIndex !== -1) {
reqList[_reqIndex].cancel()
reqList.splice(_reqIndex, 1)
// * 如果是清除多个请求
if (multi) {
manualCancelReq(url, method, multi)
}
}
}
好的,那么现在你的axios拥有了阻止多余请求的能力。
扩展
基本功能到此为止就实现了。我们可以通过这个工具实现其他的一些功能,比如下面的“当有正在进行的请求时,页面展示loading动画”
我们需要在每一个请求发起时,也就是 cancelReq
方法中执行 startLoading
方法激活 loading
动画;在请求完成时,也就是 resetReq
方法中执行 stopLoading
方法关闭 loading
动画。
当然,我们需要做一些判断,不能重复激活动画,也必须在所有请求完成后再关闭动画。同时我们可以为动画激活做一点延时,比如小于300ms的快速请求情况下,不激活动画。
假设激活和关闭动画的方法分别为 Loading.start()
和 Loading.stop()
// * 页面是否处于loading状态
let loadingStatus = false
// * 定时器
let loadingTimeout = null
/**
* * 触发loading - 延时
* * 条件:loadingStatus 为 false
* @param {Number} timeout 激活动画的延时 ms
*/
export function startLoading(timeout) {
if (!loadingStatus) {
loadingTimeout = setTimeout(() => {
Loading.start()
}, timeout || 300)
loadingStatus = true
}
}
/**
* * 结束loading
*/
export function stopLoading() {
if (loadingStatus && !reqList.length) {
clearTimeout(loadingTimeout)
Loading.stop()
loadingStatus = false
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!