#每日一记#防止按钮在短期内重复点击

每日一记 - 但并不日更

不少时候咱们点击按钮来提交数据,可是在网络条件很差或者交互提示不明确的状况下,用户会在段时间内屡次点击按钮,若是没有对按钮作保护就会形成重复的数据提交,形成数据异常,今天就分享一个比较通用的解决方案。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 源码

相关文章
相关标签/搜索