我正在尝试实现一个与javascript中的promise一起使用的去抖功能。这样,每个调用者都可以使用Promise消耗“debounced”函数的结果。这是迄今为止我能够提出的最好的:
function debounce(inner, ms = 0) {
let timer = null;
let promise = null;
const events = new EventEmitter(); // do I really need this?
return function (...args) {
if (timer == null) {
promise = new Promise(resolve => {
events.once('done', resolve);
});
} else {
clearTimeout(timer);
}
timer = setTimeout(() => {
events.emit('done', inner(...args));
timer = null;
}, ms);
return promise;
};
}
理想情况下,我想实现这个实用程序函数,而不会引入依赖于EventEmitter(或实现我自己的基本版本的EventEmitter),但我想不出办法。有什么想法吗?
答案 0 :(得分:21)
我找到了一个更好的方法来实现承诺:
TableName
我仍然欢迎建议,但是新的实现回答了我关于如何在不依赖于EventEmitter(或类似的东西)的情况下实现此函数的原始问题。
答案 1 :(得分:5)
这是我的实现,只有间隔内的最后一个调用会得到解决。在克里斯的解决方案中,所有呼叫将在它们之间延迟解决,这很好,但是有时我们只需要解决最后一个呼叫。纠正我,如果我错了。
function debounce(f, interval) {
let timer = null;
return (...args) => {
clearTimeout(timer);
return new Promise((resolve) => {
timer = setTimeout(
() => resolve(f(...args)),
interval,
);
});
};
}
答案 2 :(得分:4)
我登陆这里是因为我希望获得承诺的返回值,但是在underscore.js中的debounce正在返回undefined
。我最终使用lodash
版本,其中leading = true。它适用于我的情况,因为我不关心执行是领先还是尾随。
https://lodash.com/docs/4.17.4#debounce
_.debounce(somethingThatReturnsAPromise, 300, {
leading: true,
trailing: false
})
答案 3 :(得分:2)
如果有人需要,这是我的打字稿版本(主要基于克里斯一个)?
function promiseDebounce (exec: (...args: any[]) => Promise<any>, interval: number): () => ReturnType<typeof exec> {
let handle: number | undefined;
let resolves: Array<(value?: unknown) => void> = [];
return async (...args: unknown[]) => {
clearTimeout(handle);
handle = setTimeout(
() => {
const result = exec(...args);
resolves.forEach(resolve => resolve(result));
resolves = [];
},
interval
);
return new Promise(resolve => resolves.push(resolve));
};
}
答案 4 :(得分:0)
不知道你想要完成什么,因为它在很大程度上取决于你的需求。下面是一些有点通用的东西。如果没有牢固掌握下面代码中的内容,你可能真的不想使用它。
// Debounce state constructor
function debounce(f) {
this._f = f;
return this.run.bind(this)
}
// Debounce execution function
debounce.prototype.run = function() {
console.log('before check');
if (this._promise)
return this._promise;
console.log('after check');
return this._promise = this._f(arguments).then(function(r) {
console.log('clearing');
delete this._promise; // remove deletion to prevent new execution (or remove after timeout?)
return r;
}.bind(this)).catch(function(r) {
console.log('clearing after rejection');
delete this._promise; // Remove deletion here for as needed as noted above
return Promise.reject(r); // rethrow rejection
})
}
// Some function which returns a promise needing debouncing
function test(str) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('test' + str);
resolve();
}, 1000);
});
}
a = new debounce(test); // Create debounced version of function
console.log("p1: ", p1 = a(1));
console.log("p2: ", p2 = a(2));
console.log("p1 = p2", p1 === p2);
setTimeout(function() {
console.log("p3: ", p3 = a(3));
console.log("p1 = p3 ", p1 === p3, " - p2 = p3 ", p2 === p3);
}, 2100)
&#13;
运行上面的代码时查看控制台。我发了几条消息来说明发生了什么。首先,一些返回promise的函数作为参数传递给new debounce()
。这会创建该函数的去抖动版本。
当您按照上面的代码(a(1), a(2), and a(3)
)运行去抖动函数时,您会注意到在处理过程中它返回相同的承诺而不是开始新的承诺。一旦承诺完成,它就会删除旧承诺。在上面的代码中,我在运行a(3)之前使用setTimeout手动等待超时。
您也可以通过其他方式清除承诺,例如在debounce.prototype上添加重置或清除功能以在不同时间清除承诺。您也可以将其设置为超时。控制台日志中的测试应该显示p1和p2得到相同的承诺(参考比较&#34; ===&#34;是真的)并且p3是不同的。
答案 5 :(得分:0)
这可能不是您想要的,但是可以为您提供一些线索:
/**
* Call a function asynchronously, as soon as possible. Makes
* use of HTML Promise to schedule the callback if available,
* otherwise falling back to `setTimeout` (mainly for IE<11).
* @type {(callback: function) => void}
*/
export const defer = typeof Promise=='function' ?
Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
答案 6 :(得分:0)
这是我想出的解决此问题的方法。批处理到同一调用的所有去抖动功能的调用都返回相同的Promise,以解决将来的调用结果。
function makeFuture() {
let resolve;
let reject;
let promise = new Promise((d, e) => {
resolve = d;
reject = e;
});
return [promise, resolve, reject];
}
function debounceAsync(asyncFunction, delayMs) {
let timeout;
let [promise, resolve, reject] = makeFuture();
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(async () => {
const [prevResolve, prevReject] = [resolve, reject];
[promise, resolve, reject] = makeFuture();
try {
prevResolve(await asyncFunction.apply(this, args));
} catch (error) {
prevReject(error);
}
}, delayMs);
return promise;
}
}
const start = Date.now();
const dog = {
sound: 'woof',
bark() {
const delay = Date.now() - start;
console.log(`dog says ${this.sound} after ${delay} ms`);
return delay;
},
};
dog.bark = debounceAsync(dog.bark, 50);
Promise.all([dog.bark(), dog.bark()]).then(([delay1, delay2]) => {
console.log(`Delay1: ${delay1}, Delay2: ${delay2}`);
});
答案 7 :(得分:0)
Chris和НиколайГордеев都有很好的解决方案。第一个解决所有问题。问题是它们全部得到解决,但通常您不希望所有这些都运行。
第二个解决方案解决了这个问题,但创建了一个新问题-现在您将有多个等待。如果该函数被称为很多函数(例如搜索类型),则可能存在内存问题。我通过创建以下asyncDebounce
来解决此问题,该问题将解决最后一个并拒绝(并且await
ing调用将获得一个他们可以捕获的异常)。
const debounceWithRejection = (
inner,
ms = 0,
reject = false,
rejectionBuilder
) => {
let timer = null;
let resolves = [];
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
const resolvesLocal = resolves;
resolves = [];
if (reject) {
const resolve = resolvesLocal.pop();
resolve.res(inner(...args));
resolvesLocal.forEach((r, i) => {
!!rejectionBuilder ? r.rej(rejectionBuilder(r.args)) : r.rej(r.args);
});
} else {
resolvesLocal.forEach((r) => r.res(inner(...args)));
}
resolves = [];
}, ms);
return new Promise((res, rej) =>
resolves.push({ res, rej, args: [...args] })
);
};
};
拒绝逻辑是可选的,rejectionBuilder
也是如此。您可以选择拒绝特定的构建者,这样您就知道要抓住它。
您可以看到runing example。
答案 8 :(得分:0)
解决一个承诺,取消其他承诺
我见过的许多实现都使问题过于复杂或存在其他卫生问题。在这篇文章中,我们将编写自己的 debounce
。此实现将 -
我们用它的两个参数编写 debounce
,task
去抖动,以及延迟的毫秒数,ms
。我们为其本地状态引入了一个本地绑定,t
-
function debounce (task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return async (...args) => {
try {
t.cancel()
t = deferred()
await t.promise
await task(...args)
}
catch (_) { /* prevent memory leak */ }
}
}
我们依赖于一个可重用的 deferred
函数,该函数创建了一个在 ms
毫秒内解析的新承诺。它引入了两个本地绑定,promise
本身,cancel
它的能力 -
function deferred (ms) {
let cancel, promise = new Promise((resolve, reject) => {
cancel = reject
setTimeout(resolve, ms)
})
return { promise, cancel }
}
点击计数器示例
在第一个示例中,我们有一个按钮来计算用户的点击次数。事件侦听器使用 debounce
附加,因此计数器仅在指定的持续时间后递增 -
// debounce, deferred
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 } }
// dom references
const myform = document.forms.myform
const mycounter = myform.mycounter
// event handler
function clickCounter (event) {
mycounter.value = Number(mycounter.value) + 1
}
// debounced listener
myform.myclicker.addEventListener("click", debounce(clickCounter, 1000))
<form id="myform">
<input name="myclicker" type="button" value="click" />
<output name="mycounter">0</output>
</form>
实时查询示例,“自动完成”
在第二个示例中,我们有一个带有文本输入的表单。我们的 search
查询使用 debounce
-
// debounce, deferred
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 } }
// dom references
const myform = document.forms.myform
const myresult = myform.myresult
// event handler
function search (event) {
myresult.value = `Searching for: ${event.target.value}`
}
// debounced listener
myform.myquery.addEventListener("keypress", debounce(search, 1000))
<form id="myform">
<input name="myquery" placeholder="Enter a query..." />
<output name="myresult"></output>
</form>