JS实现”上次操作未完成禁止新操作”逻辑特事特办方案

场景

相信很多人都遇到过类似的场景:

某一个按钮是用来发送请求的,并且需要一段时间来处理。但是用户往往会在处理期间有意或无意地点击多次,因此我们希望在上一次发出的请求处理完毕之前,不再发出新的请求。

1.初步解决方案:特事特办

“特事特办”的意思,就是每次遇到这样的场景,都特意写一段逻辑来处理:

  1. document.addEventListener(‘click’, (() => {
  2.      let lock = false;
  3.      return () => {
  4.          if(lock) return;
  5.          lock = true;
  6.          console.log(‘clicked’);
  7.          // 为了方便测试就使用延时了
  8.          setTimeout(() => {
  9.              lock = false;
  10.          }, Math.random() * 4e3)
  11.      }
  12. })());

2. 基于约定回调的条件式回调函数

上面的写法其实也不费事,但是这种条件限制能不能像已经被面试问烂了的“节流”和“防抖”那样,用一个函数把它包裹起来就可以达成效果呢?
问题的关键其实在于:防抖和节流需要考虑的执行条件是时间,这个条件对于所有函数而言都是一个“共同的语言”,因此才双方可以做到那样的“默契”。
而要在这种场景里实现同样的效果,双方需要刻意的约定:例如被条件执行的函数额外接受一个函数,用于在合适的时机解除条件限制。

  1. function conditioned(callback🙁release:Function,…args:any[]) => any){
  2.      let lock = false;
  3.          return function(…args:any[]){
  4.          if(lock) return;
  5.          lock = true;
  6.          callback.call(this, () => {
  7.                  lock = false;
  8.          }, args);
  9.          }
  10. }

为了方便描述这种约定对callback 的要求,这里使用 TS 而不是 js。

使用的时候:

  1. button.addEventListener(‘click’, conditioned((release) => {
  2.      return () => {
  3.          console.log(‘clicked’);
  4.          setTimeout(() => {
  5.              release(); // 释放 lock
  6.          }, Math.random() * 4e3);
  7.      }
  8. }));

3. 基于 Promise 的条件式回调函数

如果说有什么方法能比回调函数更“优雅”、更“通用”一些,答案显然是Promise

因为上面的写法对原来的回调函数的参数进行了改写,遇到一个爱好 Ctrl + C 的初学者的话,他会疑惑复制过来的函数为什么就不工作了。

  1. function conditioned(callback:(…args:any[]) => Promise<any>){
  2.      let lock = false;
  3.      return function(…args:any[]){
  4.          if(lock) return;
  5.          lock = true;
  6.          try {
  7.              await callback.call(this, args);
  8.              lock = false;
  9.          } catch(err) {
  10.              lock = false;
  11.              throw err;
  12.          }
  13.      }
  14. }

使用方法:

  1. button.addEventListener(‘click’, conditioned(() => {
  2.      return new Promise((resolve) => {
  3.          console.log(‘clicked’);
  4.          setTimeout(() => {
  5.              resolve(); // 释放 lock
  6.          }, Math.random() * 4e3);
  7.      });
  8. }));

虽然乍一看,使用这个函数意味着必须把回调函数的返回值改写成 Promise,不过由于这种场景往往都是异步操作,改成 async 何乐而不为呢?

4. React hook 版

  1. import { useRef } from ‘react’;
  2. function useCondition(callback: (…args: any[]) => Promise<any>) {
  3.      const lock = useRef(false);
  4.      return async (…args:any[]) => {
  5.      if(lock.current) return;
  6.      lock.current = true;
  7.      try{
  8.          await callback(…args);
  9.          lock.current = false
  10.      } catch(error){
  11.          lock.current = false;
  12.          throw error;
  13.      }
  14.      };
  15. }

使用方法:

  1. <button
  2.      onClick={useCondition(() => {
  3.      return new Promise<void>((resolve) => {
  4.          console.log(‘clicked’);
  5.          setTimeout(() => {
  6.              resolve(); // 释放 lock
  7.          }, Math.random() * 4e3);
  8.      });
  9.      })}
  10. >测试</button>

实际上没测试过,不知道行不行的,更多关于JS 上次未完成禁止新操作的资料请关注我们其它相关文章!

标签

发表评论