场景
相信很多人都遇到过类似的场景:
某一个按钮是用来发送请求的,并且需要一段时间来处理。但是用户往往会在处理期间有意或无意地点击多次,因此我们希望在上一次发出的请求处理完毕之前,不再发出新的请求。
1.初步解决方案:特事特办
“特事特办”的意思,就是每次遇到这样的场景,都特意写一段逻辑来处理:
1 2 3 4 5 6 7 8 9 10 11 12 | document.addEventListener( 'click' , (() => { let lock = false ; return () => { if (lock) return ; lock = true ; console.log( 'clicked' ); // 为了方便测试就使用延时了 setTimeout(() => { lock = false ; }, Math.random() * 4e3) } })()); |
2. 基于约定回调的条件式回调函数
上面的写法其实也不费事,但是这种条件限制能不能像已经被面试问烂了的“节流”和“防抖”那样,用一个函数把它包裹起来就可以达成效果呢?
问题的关键其实在于:防抖和节流需要考虑的执行条件是时间,这个条件对于所有函数而言都是一个“共同的语言”,因此才双方可以做到那样的“默契”。
而要在这种场景里实现同样的效果,双方需要刻意的约定:例如被条件执行的函数额外接受一个函数,用于在合适的时机解除条件限制。
1 2 3 4 5 6 7 8 9 10 | function conditioned(callback:(release:Function,...args:any[]) => any){ let lock = false ; return function (...args:any[]){ if (lock) return ; lock = true ; callback.call( this , () => { lock = false ; }, ...args); } } |
为了方便描述这种约定对
callback
的要求,这里使用 TS 而不是 JS。
使用的时候:
1 2 3 4 5 6 7 8 | button.addEventListener( 'click' , conditioned((release) => { return () => { console.log( 'clicked' ); setTimeout(() => { release(); // 释放 lock }, Math.random() * 4e3); } })); |
3. 基于 Promise 的条件式回调函数
如果说有什么方法能比回调函数更“优雅”、更“通用”一些,答案显然是Promise
。
因为上面的写法对原来的回调函数的参数进行了改写,遇到一个爱好 Ctrl + C 的初学者的话,他会疑惑复制过来的函数为什么就不工作了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function conditioned(callback:(...args:any[]) => Promise){ let lock = false ; return function (...args:any[]){ if (lock) return ; lock = true ; try { await callback.call( this , ...args); lock = false ; } catch (err) { lock = false ; throw err; } } } |
使用方法:
1 2 3 4 5 6 7 8 | button.addEventListener( 'click' , conditioned(() => { return new Promise((resolve) => { console.log( 'clicked' ); setTimeout(() => { resolve(); // 释放 lock }, Math.random() * 4e3); }); })); |
虽然乍一看,使用这个函数意味着必须把回调函数的返回值改写成 Promise
,不过由于这种场景往往都是异步操作,改成 async
何乐而不为呢?
4. React hook 版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import { useRef } from 'react' ; function useCondition(callback: (...args: any[]) => Promise) { const lock = useRef( false ); return async (...args:any[]) => { if (lock.current) return ; lock.current = true ; try { await callback(...args); lock.current = false } catch (error){ lock.current = false ; throw error; } }; } |
使用方法:
1 2 3 4 5 6 7 8 9 | <button> { return new Promise((resolve) => { console.log( 'clicked' ); setTimeout(() => { resolve(); // 释放 lock }, Math.random() * 4e3); }); })} >测试</button> |
实际上没测试过,不知道行不行的,更多关于JS 上次未完成禁止新操作的资料请关注IT俱乐部其它相关文章!