现在我们都在使用Promise
许多我见过的实现方法都过于复杂或存在其他卫生问题。现在是2021年,我们已经长期使用Promise了 - 也有很好的原因。Promise可以清理异步程序,并减少出错的机会。在本文中,我们将编写自己的debounce
。这个实现将会:
- 在任何时候(每个被防抖的任务)只有一个待处理的promise
- 通过正确取消待处理的promise来停止内存泄漏
- 仅解决最新的promise
- 通过实时代码演示演示正确的行为
我们使用task
和延迟毫秒数ms
作为debounce
的两个参数进行编写。我们引入一个单一的局部绑定t
作为它的局部状态 -
function debounce (task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return async (...args) => {
try {
t.cancel()
t = deferred(ms)
await t.promise
await task(...args)
}
catch (_) { }
}
}
我们依赖于一个可重复使用的
deferred
函数,该函数创建一个在
ms
毫秒后解决的新的promise。它引入了两个本地绑定,即
promise
本身和取消它的能力。
function deferred (ms) {
let cancel, promise = new Promise((resolve, reject) => {
cancel = reject
setTimeout(resolve, ms)
})
return { promise, cancel }
}
点击计数器示例
在这个第一个示例中,我们有一个按钮来统计用户的点击次数。事件监听器使用debounce
进行附加,因此只有在指定的持续时间后才会增加计数器 -
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }
const myform = document.forms.myform
const mycounter = myform.mycounter
function clickCounter (event) {
mycounter.value = Number(mycounter.value) + 1
}
myform.myclicker.addEventListener("click", debounce(clickCounter, 1000))
<form id="myform">
<input name="myclicker" type="button" value="click" />
<output name="mycounter">0</output>
</form>
现场查询示例,“自动完成”
在这个第二个例子中,我们有一个带有文本输入的表单。我们使用debounce
附加了我们的search
查询 -
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }
const myform = document.forms.myform
const myresult = myform.myresult
function search (event) {
myresult.value = `Searching for: ${event.target.value}`
}
myform.myquery.addEventListener("keypress", debounce(search, 1000))
<form id="myform">
<input name="myquery" placeholder="Enter a query..." />
<output name="myresult"></output>
</form>
多重防抖,React钩子useDebounce
在另一个问答中,有人询问是否可以使用暴露出的防抖取消机制来创建一个useDebounce
React钩子。使用上面的deferred
,这是一项微不足道的练习。
function debounce(task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return [
_ => t.cancel()
]
}
const [inc, cancel] = debounce(clickCounter, 1000)
myform.mybutton.addEventListener("click", inc)
myform.mycancel.addEventListener("click", cancel)
实现一个useDebounce
的React hook非常简单 -
function useDebounce(task, ms) {
const [f, cancel] = debounce(task, ms)
useEffect(_ => cancel)
return [f, cancel]
}
请前往原始问答查看完整演示。
clearTimeout
,它将不会执行任何操作。 - Ry-