#每日一記#防止按鈕在短期內重複點擊

每日一記 - 但並不日更

不少時候咱們點擊按鈕來提交數據,可是在網絡條件很差或者交互提示不明確的狀況下,用戶會在段時間內屢次點擊按鈕,若是沒有對按鈕作保護就會形成重複的數據提交,形成數據異常,今天就分享一個比較通用的解決方案。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

demo 源碼

相關文章
相關標籤/搜索