JS按鈕連擊和接口調(diào)用頻率限制防止客戶爆倉
背景
這個項目是一個貨幣交易客戶端,后端會走幣安的開放接口,而幣安的接口每分鐘調(diào)用次數(shù)是有閾值的,調(diào)多了直接接口返回錯誤。
客戶端里,有的窗口可能涉及 多個信息的查詢 ,而這些信息需要調(diào)用不同的幣安的接口,因此后端有的接口調(diào)用起來 權(quán)重很大(存在一個接口需要調(diào)用幣安十幾個接口的情況)。
那么接口調(diào)用權(quán)重大的有兩個窗口,其中一個是賬戶信息窗口。
賬戶信息窗口需要實時的更新持倉盈虧以及強平價、開倉價等信息,這些信息分布在幣安各個接口里,所以調(diào)用這個接口的 權(quán)重很大 。在這個窗口中我們添加了一個 強制刷新數(shù)據(jù) 按鈕,用來 防止行情波動大 時卡住,影響 數(shù)據(jù)實時性。
那么當(dāng)時的我還是欠考慮,忘記 給按鈕添加防抖操作了,帶來的結(jié)果就是在網(wǎng)絡(luò)狀況不好的情況下,有些比較急躁的用戶會 連擊 ,這樣會一直調(diào)用接口,權(quán)重很快就達到閾值了。達到閾值后平倉平不了,虧錢甚至是爆倉,只能干瞪眼。
所以我們要 控制用戶連擊行為 ,這就要用到節(jié)流了。
另一個調(diào)用權(quán)重大的窗口是交易窗口,委托下單成功后會推送持倉數(shù)據(jù)、開倉價等。委托單有幾個狀態(tài):掛單、部成(部分成交,多次)或者已成(完全成交,一次),部成狀態(tài)和已成狀態(tài)都會推送數(shù)據(jù),有推送就要調(diào)接口。那么部成的情況下就很容易短時間內(nèi)(0.5s)達到完全成交,也就是說有可能 一個委托單會觸發(fā)好幾次的接口調(diào)用 。這種客戶端主要功能就是 下單 了,行情波動大的時候交易員都是快捷鍵操作,一秒幾單,這是達到閾值的主要原因,不節(jié)流等著提桶吧。
節(jié)流是什么
介紹了這么多,有的小伙伴還不知道什么是“節(jié)流”,或者是聽過 防抖 和 節(jié)流 ,但是一直對這兩個概念混淆,接下來我額外給大家做個小科普。
想必很多人都有玩過 moba 游戲,我拿大眾點的 英雄聯(lián)盟 和 王者榮耀 來舉例。
節(jié)流:英雄是會釋放技能的,技能釋放完會有冷卻 cd,如果沒有冷卻完畢,不管你手按的再快,技能都放不出來。這個就是節(jié)流,一定時間瘋狂連擊我只觸發(fā)一次。
防抖:回城都知道吧,王者榮耀里回城所需的時間是 7 秒,如果在回城過程中你再次點擊回城,那么回城時間是會被重置的。比如你點擊回城過了 3 秒了,這個時間手欠又點了一下回城,好了,原本只要再等 3 秒就能泡泉水,這下你又要重新登 7 秒了。這個就是防抖。
回歸正題,因為我希望的是 允許用戶刷新,但是不能太頻繁,最好是一段時間內(nèi)只允許刷新一次 ,是不是和上面防抖的例子一樣,妥妥的防抖就安排上了嘛。
如何節(jié)流
不使用節(jié)流
我們先使用一個簡單的例子來講。
邏輯就是鼠標(biāo)在灰色 box 上移動時,不斷遞增數(shù)字。
<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 事件會頻繁觸發(fā)。如果換成接口調(diào)用會咋樣?想都不敢想。
使用節(jié)流之后
我們的需求還是鼠標(biāo)移動時,數(shù)字遞增,不同的是我們希望數(shù)字增長不要太快(事件觸發(fā)頻繁不要太快),這就要用到防抖了。
我們來改造一下代碼:
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 函數(shù)的返回值是一個函數(shù),這個函數(shù)引用了外層變量 time,形成了一個閉包。
變量 time 用來記錄 上一次調(diào)用發(fā)生的時間 ,一開始默認(rèn)為 0 ,這樣下次觸發(fā)就能 直接進行第一次調(diào)用 。
后續(xù)觸發(fā)事件回調(diào)時,判斷當(dāng)前觸發(fā)回調(diào)的時間和上一次觸發(fā)回調(diào)的 時間差 是不是 大于 我們規(guī)定的時間(0.5s),如果大于則允許調(diào)用,否則本著節(jié)流的邏輯,這次調(diào)用顯然不被允許了。
需要注意的是,在允許調(diào)用的情況下,我們要 更新 time 的值為 now。
我們來看看改造后的效果:

模板
相信大家都看出來了,樸素的節(jié)流有一套模板:
const thrrotle = (callback) => {
let time = 0;
return () => {
const now = Date.now();
const diff = (now - time) / 1000;
if(diff > 0.5) {
callback();
time = now;
}
}
}
還有種節(jié)流是通過一個 flag 變量控制是否允許調(diào)用回調(diào)的:
function throttle(fn,delay) {
let flag = true;
return function() {
if (flag) {
setTimeout(() => {
fn.call(this); // 綁定 this
flag = true;
}, delay);
}
flag = false;
}
}
示例
那么我項目中就是控制 10 秒內(nèi)只允許觸發(fā)一次接口調(diào)用,因此這里的 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(); // 調(diào)用接口
lastTime = now;
} else {
message.info({
content: `刷新過于頻繁,請${delay - Math.floor(diff)}秒后嘗試!`,
key: EMessageKey.ACCOUNT_INFO,
});
}
};
})();
經(jīng)過這么一改造,用戶第一次點擊刷新的時候是允許刷新的,而在 10 秒內(nèi)妄圖再次刷新,展現(xiàn)給它的只有冰冷的提示語。
結(jié)束語
日常開發(fā)中,除了限制接口調(diào)用頻率外,像頁面 scroll 事件、窗口 resize 事件,為了性能考慮,都是需要進行節(jié)流處理的,而看完本文,相信大家都理解掌握了節(jié)流的方法,套用模板就完事了。但是還是希望大家能吃透,畢竟代碼也不多,有了思路就不用去背代碼了。學(xué)到就是賺到。
以上就是JS按鈕連擊和接口調(diào)用頻率限制防止客戶爆倉的詳細(xì)內(nèi)容,更多關(guān)于JS限制按鈕連擊接口調(diào)用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS前端使用canvas動態(tài)繪制函數(shù)曲線示例詳解
這篇文章主要為大家介紹了JS前端使用canvas畫函數(shù)曲線的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08

