JS前端無侵入實現防止重復提交請求技術
最近在代碼發布測試的過程中,我發現有些請求非常的消耗服務器資源,而系統測試人員因為響應太慢而不停的點擊請求。我是很看不慣系統存在不順眼的問題,做事喜歡精益求精,也很喜歡和別人爭論技術,有時候硬要爭得你死我活。
實在看不下去系統存在這個問題,下定決心好好整改一波,我們系統幾乎都是使用Ajax技術發請求,使用的是針對我們系統特點對jQuery的post方法進行了封裝的newpost方法,這個方法擴展了異常響應,登陸超時,token驗證,加載時渲染等自動完成功能,參數不變,也是我寫的。由於系統模塊很多,功能很強大,代碼也很多,為了統一使用此方法,花了不少時間將post修改成newpost。這次為了限制請求次數這個小功能,我請示了領導並把初步的方案說與他聽,他同意了我做此次修改。
我調查了幾個有防止請求重發控制的頁面,幾乎都是發起請求后到響應這段時間對按鈕置灰警用處理,這種方案有很多的缺點,如:
1. 長時間未響應的請求給人不友好的體驗
2. 需要針對每個按鈕做請求前后的控制,會因為處理各種小問題而變得難以維護
3. 業務與控制邏輯混合在一起使得函數功能不單一
我最初的想法就是想找到一種類似於面向切面的無侵入頁面的方式實現請求限制,因此想到了如下方案:
1. 只控制用戶最后一次的請求
2. 第一次請求未響應之前一分鍾之內不發相同的請求
3. 如果前一次請求得到了響應,那么第二次相同的請求也不做限制
后來發現這種方案行不通,主要有以下幾個方面的原因:
1. 大多數頁面每交互一次都會發起多個不同的請求。
2. 沒個請求響應的時間也不一樣,可能后發的請求先響應
我把這種想法告訴了導師,經過一番爭論后認為我無法通過不侵入的方式實現這樣的功能,按照他的想法,我得修改整個項目有請求的代碼,完全低估了我對JS的了解,后來我獨自優化了方案,不在只控制最后一個請求,代碼實現的大綱如下:
發起POST請求前處理邏輯
1. 獲取全局上下文中的存儲請求信息的數組變量
2. 遍歷請求響應數組
3. 清理大於兩分鍾未響應的請求,防止頁面長時間不響應
4. 找到了與本次相同的請求
a) 如果請求小於200ms,則判斷為系統行為,直接將環境和回調函數壓入已有請求的調用棧,否則替換之前的請求的調用棧
b) 不發請求
5. 沒有找到與本次相同的請求
a) 將請求信息、時間、調用環境和回調函數壓進請求響應數組,請求URL和條件con作為唯一標識,調用棧也是一個由當前環境和回調函數組成的數組,使用數組解決相同的請求(url和con都一樣)
b) 發起請求
請求響應后處理邏輯
1. 獲取全局上下文中的存儲請求信息的數組變量
2. 遍歷請求響應數組
3. 找到了與此響應相同的請求
a) 遍歷此請求的調用棧並依次調用回調函數
b) 刪除已經處理的請求
4. 沒有找到與此響應相同的請求則什么都不做
具體實現:
/*@description 自定義事件 *@method_costomEvent *@formuphy */ muphypost: function (url, condition, callback, type, option) { if (typeof condition === 'function') { option = type; type = callback; callback = condition; condition = {};//jQuery:condition = undefine; } let that = this, reqUrl = (url + JSON.stringify(condition)).substr(0, 1000); //判斷是否需要發起請求 let hasReq = beforePost(); if (hasReq === true) { return that; } //新增請求Token muphy.addToken(condition); //借助jQuery發請求 $.post(url, condition, function (data, status){ afterPost(data, status); }, type); return that; //請求前處理函數 function beforePost() { //獲取歷史請求 let postHistory = muphy.postHistory, now = new Date(); //遍歷請求響應數組,找到與此請求相同的請求 for (let i = 0; i < postHistory.length;i++) { if (now - postHistory[i]._startTime > 120000) { //清理大於兩分鍾未響應的請求 postHistory.splice(i, 1); } elseif (postHistory[i]._reqUrl === reqUrl){ //找到了就更新請求時間,並將執行環境和回調函數放入請求響應數組的調用棧; //小於200ms判定為系統行為直接將環境和回調函數壓入已有請求的調用棧, //否則替換之前的請求 if (now - postHistory[i]._startTime < 200) { postHistory[i]._startTime =now; postHistory[i]._handles.push({ _callback: callback, _this: that }); } else { postHistory[i]._handles =[{ _callback: callback, _this: that }]; } //返回true 表示本次不發請求 return true; } }; //沒找到與此相同的請求時將請求信息、時間、調用環境和回調函數壓進請求響應數組 postHistory.push({ _reqUrl: reqUrl,_startTime: new Date(), _handles: [{ _callback:callback, _this: that }] }); //返回false 表示本次需要發請求 return false; } //請求后處理函數 function afterPost(data, status) { //獲取請求響應數組 let postHistory = muphy.postHistory; //遍歷請求響應數組,找到與此請求相同的請求 for (let i = 0; i < postHistory.length;i++) { if (postHistory[i]._reqUrl === reqUrl) { //找到相同的請求,遍歷此請求的調用棧並調用 muphy.each(postHistory[i]._handles, function () { this._callback.call(this._this, data, status); }); //調用完成刪除已經處理的請求 postHistory.splice(i, 1); break; } } } }