JS按鈕連擊和接口調(diào)用頻率限制防止客戶爆倉(cāng)
背景
這個(gè)項(xiàng)目是一個(gè)貨幣交易客戶端,后端會(huì)走幣安的開(kāi)放接口,而幣安的接口每分鐘調(diào)用次數(shù)是有閾值的,調(diào)多了直接接口返回錯(cuò)誤。
客戶端里,有的窗口可能涉及 多個(gè)信息的查詢 ,而這些信息需要調(diào)用不同的幣安的接口,因此后端有的接口調(diào)用起來(lái) 權(quán)重很大(存在一個(gè)接口需要調(diào)用幣安十幾個(gè)接口的情況)。
那么接口調(diào)用權(quán)重大的有兩個(gè)窗口,其中一個(gè)是賬戶信息窗口。
賬戶信息窗口需要實(shí)時(shí)的更新持倉(cāng)盈虧以及強(qiáng)平價(jià)、開(kāi)倉(cāng)價(jià)等信息,這些信息分布在幣安各個(gè)接口里,所以調(diào)用這個(gè)接口的 權(quán)重很大 。在這個(gè)窗口中我們添加了一個(gè) 強(qiáng)制刷新數(shù)據(jù) 按鈕,用來(lái) 防止行情波動(dòng)大 時(shí)卡住,影響 數(shù)據(jù)實(shí)時(shí)性。
那么當(dāng)時(shí)的我還是欠考慮,忘記 給按鈕添加防抖操作了,帶來(lái)的結(jié)果就是在網(wǎng)絡(luò)狀況不好的情況下,有些比較急躁的用戶會(huì) 連擊 ,這樣會(huì)一直調(diào)用接口,權(quán)重很快就達(dá)到閾值了。達(dá)到閾值后平倉(cāng)平不了,虧錢(qián)甚至是爆倉(cāng),只能干瞪眼。
所以我們要 控制用戶連擊行為 ,這就要用到節(jié)流了。
另一個(gè)調(diào)用權(quán)重大的窗口是交易窗口,委托下單成功后會(huì)推送持倉(cāng)數(shù)據(jù)、開(kāi)倉(cāng)價(jià)等。委托單有幾個(gè)狀態(tài):掛單、部成(部分成交,多次)或者已成(完全成交,一次),部成狀態(tài)和已成狀態(tài)都會(huì)推送數(shù)據(jù),有推送就要調(diào)接口。那么部成的情況下就很容易短時(shí)間內(nèi)(0.5s)達(dá)到完全成交,也就是說(shuō)有可能 一個(gè)委托單會(huì)觸發(fā)好幾次的接口調(diào)用 。這種客戶端主要功能就是 下單 了,行情波動(dòng)大的時(shí)候交易員都是快捷鍵操作,一秒幾單,這是達(dá)到閾值的主要原因,不節(jié)流等著提桶吧。
節(jié)流是什么
介紹了這么多,有的小伙伴還不知道什么是“節(jié)流”,或者是聽(tīng)過(guò) 防抖 和 節(jié)流 ,但是一直對(duì)這兩個(gè)概念混淆,接下來(lái)我額外給大家做個(gè)小科普。
想必很多人都有玩過(guò) moba 游戲,我拿大眾點(diǎn)的 英雄聯(lián)盟 和 王者榮耀 來(lái)舉例。
節(jié)流:英雄是會(huì)釋放技能的,技能釋放完會(huì)有冷卻 cd,如果沒(méi)有冷卻完畢,不管你手按的再快,技能都放不出來(lái)。這個(gè)就是節(jié)流,一定時(shí)間瘋狂連擊我只觸發(fā)一次。
防抖:回城都知道吧,王者榮耀里回城所需的時(shí)間是 7 秒,如果在回城過(guò)程中你再次點(diǎn)擊回城,那么回城時(shí)間是會(huì)被重置的。比如你點(diǎn)擊回城過(guò)了 3 秒了,這個(gè)時(shí)間手欠又點(diǎn)了一下回城,好了,原本只要再等 3 秒就能泡泉水,這下你又要重新登 7 秒了。這個(gè)就是防抖。
回歸正題,因?yàn)槲蚁M氖?允許用戶刷新,但是不能太頻繁,最好是一段時(shí)間內(nèi)只允許刷新一次 ,是不是和上面防抖的例子一樣,妥妥的防抖就安排上了嘛。
如何節(jié)流
不使用節(jié)流
我們先使用一個(gè)簡(jiǎn)單的例子來(lái)講。
邏輯就是鼠標(biāo)在灰色 box 上移動(dòng)時(shí),不斷遞增數(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
事件會(huì)頻繁觸發(fā)。如果換成接口調(diào)用會(huì)咋樣?想都不敢想。
使用節(jié)流之后
我們的需求還是鼠標(biāo)移動(dòng)時(shí),數(shù)字遞增,不同的是我們希望數(shù)字增長(zhǎng)不要太快(事件觸發(fā)頻繁不要太快),這就要用到防抖了。
我們來(lái)改造一下代碼:
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ù)的返回值是一個(gè)函數(shù),這個(gè)函數(shù)引用了外層變量 time
,形成了一個(gè)閉包。
變量 time
用來(lái)記錄 上一次調(diào)用發(fā)生的時(shí)間 ,一開(kāi)始默認(rèn)為 0
,這樣下次觸發(fā)就能 直接進(jìn)行第一次調(diào)用 。
后續(xù)觸發(fā)事件回調(diào)時(shí),判斷當(dāng)前觸發(fā)回調(diào)的時(shí)間和上一次觸發(fā)回調(diào)的 時(shí)間差 是不是 大于 我們規(guī)定的時(shí)間(0.5s),如果大于則允許調(diào)用,否則本著節(jié)流的邏輯,這次調(diào)用顯然不被允許了。
需要注意的是,在允許調(diào)用的情況下,我們要 更新 time
的值為 now
。
我們來(lái)看看改造后的效果:
模板
相信大家都看出來(lái)了,樸素的節(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é)流是通過(guò)一個(gè) 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; } }
示例
那么我項(xiàng)目中就是控制 10 秒內(nèi)只允許觸發(fā)一次接口調(diào)用,因此這里的 0.5 我要改成 10。
// 點(diǎn)擊刷新按鈕嘗試刷新 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: `刷新過(guò)于頻繁,請(qǐng)${delay - Math.floor(diff)}秒后嘗試!`, key: EMessageKey.ACCOUNT_INFO, }); } }; })();
經(jīng)過(guò)這么一改造,用戶第一次點(diǎn)擊刷新的時(shí)候是允許刷新的,而在 10 秒內(nèi)妄圖再次刷新,展現(xiàn)給它的只有冰冷的提示語(yǔ)。
結(jié)束語(yǔ)
日常開(kāi)發(fā)中,除了限制接口調(diào)用頻率外,像頁(yè)面 scroll
事件、窗口 resize
事件,為了性能考慮,都是需要進(jìn)行節(jié)流處理的,而看完本文,相信大家都理解掌握了節(jié)流的方法,套用模板就完事了。但是還是希望大家能吃透,畢竟代碼也不多,有了思路就不用去背代碼了。學(xué)到就是賺到。
以上就是JS按鈕連擊和接口調(diào)用頻率限制防止客戶爆倉(cāng)的詳細(xì)內(nèi)容,更多關(guān)于JS限制按鈕連擊接口調(diào)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序獲取用戶openId的實(shí)現(xiàn)方法
這篇文章主要介紹了微信小程序獲取用戶openId的實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2017-05-05JavaScript 對(duì)象詳細(xì)整理總結(jié)
這篇文章主要介紹了JavaScript 對(duì)象詳細(xì)整理總結(jié)的相關(guān)資料,需要的朋友可以參考下2016-09-09JS前端使用canvas動(dòng)態(tài)繪制函數(shù)曲線示例詳解
這篇文章主要為大家介紹了JS前端使用canvas畫(huà)函數(shù)曲線的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08JS實(shí)現(xiàn)layui?table篩選框記憶功能
這篇文章主要介紹了JS實(shí)現(xiàn)layui?table篩選框記憶功能,本案例放入本地緩存的方式,使用MutationObserver實(shí)現(xiàn)監(jiān)控點(diǎn)擊事件,需要的朋友可以參考下2022-01-01javascript實(shí)現(xiàn)超炫的向上滑行菜單實(shí)例
這篇文章主要介紹了javascript實(shí)現(xiàn)超炫的向上滑行菜單實(shí)現(xiàn)方法,以一個(gè)完整實(shí)例形式分析了javascript針對(duì)頁(yè)面元素的遍歷與樣式操作相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08深入內(nèi)存原理談JS中變量存儲(chǔ)在堆中還是棧中
JavaScript中基本類(lèi)型存儲(chǔ)在堆中還是棧中,百度一下有很多不同的答案,本篇文章就來(lái)給大家為此做個(gè)詳細(xì)的介紹,需要的朋友可以參考一下2021-09-09微信小程序 后臺(tái)登錄(非微信賬號(hào))實(shí)例詳解
這篇文章主要介紹了微信小程序 后臺(tái)登錄(非微信賬號(hào))實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03