JS按钮连击和接口调用频率限制防止客户爆仓
作者:CatWatermelon
背景
这个项目是一个货币交易客户端,后端会走币安的开放接口,而币安的接口每分钟调用次数是有阈值的,调多了直接接口返回错误。
客户端里,有的窗口可能涉及 多个信息的查询 ,而这些信息需要调用不同的币安的接口,因此后端有的接口调用起来 权重很大(存在一个接口需要调用币安十几个接口的情况)。
那么接口调用权重大的有两个窗口,其中一个是账户信息窗口。
账户信息窗口需要实时的更新持仓盈亏以及强平价、开仓价等信息,这些信息分布在币安各个接口里,所以调用这个接口的 权重很大 。在这个窗口中我们添加了一个 强制刷新数据 按钮,用来 防止行情波动大 时卡住,影响 数据实时性。
那么当时的我还是欠考虑,忘记 给按钮添加防抖操作了,带来的结果就是在网络状况不好的情况下,有些比较急躁的用户会 连击 ,这样会一直调用接口,权重很快就达到阈值了。达到阈值后平仓平不了,亏钱甚至是爆仓,只能干瞪眼。
所以我们要 控制用户连击行为 ,这就要用到节流了。
另一个调用权重大的窗口是交易窗口,委托下单成功后会推送持仓数据、开仓价等。委托单有几个状态:挂单、部成(部分成交,多次)或者已成(完全成交,一次),部成状态和已成状态都会推送数据,有推送就要调接口。那么部成的情况下就很容易短时间内(0.5s)达到完全成交,也就是说有可能 一个委托单会触发好几次的接口调用 。这种客户端主要功能就是 下单 了,行情波动大的时候交易员都是快捷键操作,一秒几单,这是达到阈值的主要原因,不节流等着提桶吧。
节流是什么
介绍了这么多,有的小伙伴还不知道什么是“节流”,或者是听过 防抖 和 节流 ,但是一直对这两个概念混淆,接下来我额外给大家做个小科普。
想必很多人都有玩过 moba 游戏,我拿大众点的 英雄联盟 和 王者荣耀 来举例。
节流:英雄是会释放技能的,技能释放完会有冷却 cd,如果没有冷却完毕,不管你手按的再快,技能都放不出来。这个就是节流,一定时间疯狂连击我只触发一次。
防抖:回城都知道吧,王者荣耀里回城所需的时间是 7 秒,如果在回城过程中你再次点击回城,那么回城时间是会被重置的。比如你点击回城过了 3 秒了,这个时间手欠又点了一下回城,好了,原本只要再等 3 秒就能泡泉水,这下你又要重新登 7 秒了。这个就是防抖。
回归正题,因为我希望的是 允许用户刷新,但是不能太频繁,最好是一段时间内只允许刷新一次 ,是不是和上面防抖的例子一样,妥妥的防抖就安排上了嘛。
如何节流
不使用节流
我们先使用一个简单的例子来讲。
逻辑就是鼠标在灰色 box 上移动时,不断递增数字。
<style> .box { background-color: grey; height: 100px; display: flex; justify-content: center; align-items: center; font-size: 20px; color: #fff; } </style> <body> <div class="box" id="box">0</div> <script> const box = document.querySelector('#box'); let count = 0; box.addEventListener('mousemove', ()=>{ box.innerHTML = ++count; }) </script> </body>
可以看到,正常情况下 mousemove
事件会频繁触发。如果换成接口调用会咋样?想都不敢想。
使用节流之后
我们的需求还是鼠标移动时,数字递增,不同的是我们希望数字增长不要太快(事件触发频繁不要太快),这就要用到防抖了。
我们来改造一下代码:
const box = document.querySelector('#box'); let count = 0; const throttle = (callback) => { let time = 0; return () => { const now = Date.now(); const diff = (now - time) / 1000; if(diff > 0.5) { callback(); time = now; } } } box.addEventListener('mousemove', throttle(()=>{ box.innerHTML = ++count; }))
其中,throttle
函数的返回值是一个函数,这个函数引用了外层变量 time
,形成了一个闭包。
变量 time
用来记录 上一次调用发生的时间 ,一开始默认为 0
,这样下次触发就能 直接进行第一次调用 。
后续触发事件回调时,判断当前触发回调的时间和上一次触发回调的 时间差 是不是 大于 我们规定的时间(0.5s),如果大于则允许调用,否则本着节流的逻辑,这次调用显然不被允许了。
需要注意的是,在允许调用的情况下,我们要 更新 time
的值为 now
。
我们来看看改造后的效果:
模板
相信大家都看出来了,朴素的节流有一套模板:
const thrrotle = (callback) => { let time = 0; return () => { const now = Date.now(); const diff = (now - time) / 1000; if(diff > 0.5) { callback(); time = now; } } }
还有种节流是通过一个 flag
变量控制是否允许调用回调的:
function throttle(fn,delay) { let flag = true; return function() { if (flag) { setTimeout(() => { fn.call(this); // 绑定 this flag = true; }, delay); } flag = false; } }
示例
那么我项目中就是控制 10 秒内只允许触发一次接口调用,因此这里的 0.5 我要改成 10。
// 点击刷新按钮尝试刷新 const attempRefresh = (() => { let lastTime = new Date().getTime(); const delay = 10; return () => { const now = new Date().getTime(); const diff = (now - lastTime) / 1000; if (diff >= delay) { getAccountInfo(); // 调用接口 lastTime = now; } else { message.info({ content: `刷新过于频繁,请${delay - Math.floor(diff)}秒后尝试!`, key: EMessageKey.ACCOUNT_INFO, }); } }; })();
经过这么一改造,用户第一次点击刷新的时候是允许刷新的,而在 10 秒内妄图再次刷新,展现给它的只有冰冷的提示语。
结束语
日常开发中,除了限制接口调用频率外,像页面 scroll
事件、窗口 resize
事件,为了性能考虑,都是需要进行节流处理的,而看完本文,相信大家都理解掌握了节流的方法,套用模板就完事了。但是还是希望大家能吃透,毕竟代码也不多,有了思路就不用去背代码了。学到就是赚到。
以上就是JS按钮连击和接口调用频率限制防止客户爆仓的详细内容,更多关于JS限制按钮连击接口调用的资料请关注脚本之家其它相关文章!