不少時候咱們點擊按鈕來提交數據,可是在網絡條件很差或者交互提示不明確的狀況下,用戶會在段時間內屢次點擊按鈕,若是沒有對按鈕作保護就會形成重複的數據提交,形成數據異常,今天就分享一個比較通用的解決方案。javascript
解決這個問題的思路就是增長一個變量來維護現有按鈕的狀態,可是一個頁面裏若是有不少按鈕,那麼就要申明同等數量的變量,這對維護來講很不友好。html
##現有問題java
<div class="button">
提交
</div>
複製代碼
var button = document.querySelector('.button');
button.onclick = submit;
function submit (e) {
// 模擬異步
var promiseCb = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('提交成功');
}, 1000);
})
return promiseCb.then(
function (res) {
// 處理回調
console.log(res);
}
)
}
複製代碼
爲了更好的封裝舊的代碼,就必須避免增長額外的變量,因此寫了一個 actionDelegate 函數來對原有的 action 進行封裝,而原有的代碼只須要修改一下就能夠了node
button.onclick = submit;
↓
button.onclick = actionDelegate(submit);
function actionDelegate (action) {
// do something
}
複製代碼
而後咱們要對 action 進行類型的判斷,若是 action 返回的是普通的對象,那麼咱們認爲這個 action 是一個同步的函數;若是 action 返回的是一個 promsie,那麼咱們認爲咱們須要等待這個 promise 狀態結束後才能讓這個 action 再次執行angularjs
function actionDelegate (action) {
// 獲取函數返回值
var returnValue = action(e);
// 判斷返回值是 promise
if (returnValue && returnValue.constructor &&
returnValue.constructor.name === 'Promise') {
// 保護按鈕不被狂點
}
else {
// let it go
}
}
複製代碼
因此在 submit 函數中最終必需要返回一個 promise 就變得很重要promise
function submit (e) {
// 模擬異步
var promiseCb = new Promise(...)
return promiseCb
}
複製代碼
接着咱們就要處理最重要的部分了,那就是保存按鈕的狀態,在這個案例裏我經過 event 獲取到了按鈕的 node,而且把狀態保存在 node 的 attr 上,這邊也可使用其餘的方式去儲存狀態網絡
function actionDelegate (action) {
return function (e) {
if (e.target.getAttribute('progress-status') === 'processing') {
// 若是按鈕上有處理中的狀態則跳事後續邏輯
return false;
}
// 獲取函數返回值
var returnValue = action(e);
// 判斷返回值是 promise
if (returnValue && returnValue.constructor &&
returnValue.constructor.name === 'Promise') {
// 關鍵點 把按鈕狀態保存在 node 的屬性上
e.target.setAttribute('progress-status', 'processing')
return returnValue.then(
function () {
// promise 結束後重置狀態
e.target.setAttribute('progress-status', 'initial');
}
)
}
}
}
複製代碼
最後代碼組裝起來就是下面的樣子異步
var button = document.querySelector('.button');
button.onclick = actionDelegate(submit);
function submit (e) {
// 模擬異步
var promiseCb = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('提交成功');
}, 1000);
})
return promiseCb.then(
function (res) {
// 處理回調
console.log(res);
}
)
}
function actionDelegate (action) {
return function (e) {
if (e.target.getAttribute('progress-status') === 'processing') {
// 若是按鈕上有處理中的狀態則跳事後續邏輯
return false;
}
// 獲取函數返回值
var returnValue = action(e);
// 判斷返回值是 promise
if (returnValue && returnValue.constructor &&
returnValue.constructor.name === 'Promise') {
var originInnerHTML = e.target.innerHTML;
// 關鍵點 把按鈕狀態保存在 node 的屬性上
e.target.setAttribute('progress-status', 'processing')
e.target.innerHTML = '提交中...';
return returnValue.then(
function () {
// promise 結束後重置狀態
e.target.setAttribute('progress-status', 'initial');
e.target.innerHTML = originInnerHTML;
}
)
}
}
}
複製代碼
原本是用 angularjs 實現的一個指令,用來代替 ng-click 的,後來發如今別的項目裏也要用,因此就用原生的代碼從新實現了邏輯。在 angularjs 中可能會更好實現,由於指令自己會有獨立的做用域就不須要重複申明變量了。今天在寫教程的時候忽然發現用 attr 來實現可能更方便,各位若是有更好的實現方式能夠來交流。函數
謝謝ui
若是喜歡這篇文章 能夠關注專欄 也請點贊分享哦
##JSbin