JS按钮连击和接口调用频率限制防止客户爆仓

 

背景

这个项目是一个货币交易客户端,后端会走币安的开放接口,而币安的接口每分钟调用次数是有阈值的,调多了直接接口返回错误。

客户端里,有的窗口可能涉及 多个信息的查询 ,而这些信息需要调用不同的币安的接口,因此后端有的接口调用起来 权重很大(存在一个接口需要调用币安十几个接口的情况)。

那么接口调用权重大的有两个窗口,其中一个是账户信息窗口。

账户信息窗口需要实时的更新持仓盈亏以及强平价、开仓价等信息,这些信息分布在币安各个接口里,所以调用这个接口的 权重很大 。在这个窗口中我们添加了一个 强制刷新数据 按钮,用来 防止行情波动大 时卡住,影响 数据实时性

那么当时的我还是欠考虑,忘记 给按钮添加防抖操作了,带来的结果就是在网络状况不好的情况下,有些比较急躁的用户会 连击 ,这样会一直调用接口,权重很快就达到阈值了。达到阈值后平仓平不了,亏钱甚至是爆仓,只能干瞪眼。

所以我们要 控制用户连击行为 ,这就要用到节流了。

另一个调用权重大的窗口是交易窗口,委托下单成功后会推送持仓数据、开仓价等。委托单有几个状态:挂单、部成(部分成交,多次)或者已成(完全成交,一次),部成状态和已成状态都会推送数据,有推送就要调接口。那么部成的情况下就很容易短时间内(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限制按钮连击接口调用的资料请关注编程宝库其它相关文章!

 去除js中的json存在的转义字符\js中打印出的示例执行console.log(JSON.stringify(d).toString()),得到以下信息:{"businessNum ...