Skip to content

Commit 12c980b

Browse files
committed
feat(core utils): debouncer - Add postpone option for callback to be run after all debounce calls or in between.
If "postpone" is set to "true" (the default and previous behavior) the callback will only be called after no more debouncer calls are done for the given timeout. If "postpone" is set to "false" the callback will be run after the timeout has passed and calls to "debouncer" in between are ignored.
1 parent 14af661 commit 12c980b

File tree

2 files changed

+42
-4
lines changed

2 files changed

+42
-4
lines changed

src/core/utils.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,20 @@ const timeout = (ms) => {
516516
return new Promise((resolve) => setTimeout(resolve, ms));
517517
};
518518

519-
const debounce = (func, ms, timer = { timer: null }) => {
519+
/**
520+
* Returns a function, that, as long as it continues to be invoked, will not
521+
* be triggered. The function will be called after it stops being called for
522+
* N milliseconds.
523+
* From: https://underscorejs.org/#debounce
524+
*
525+
* @param {Function} func - The function to debounce.
526+
* @param {Number} ms - The time in milliseconds to debounce.
527+
* @param {Object} timer - A module-global timer as an object.
528+
* @param {Boolean} postpone - If true, the function will be called after it stops being called for N milliseconds.
529+
*
530+
* @returns {Function} - The debounced function.
531+
*/
532+
const debounce = (func, ms, timer = { timer: null }, postpone = true) => {
520533
// Returns a function, that, as long as it continues to be invoked, will not
521534
// be triggered. The function will be called after it stops being called for
522535
// N milliseconds.
@@ -528,11 +541,17 @@ const debounce = (func, ms, timer = { timer: null }) => {
528541
//
529542
// Pass a module-global timer as an object ``{ timer: null }`` if you want
530543
// to also cancel debounced functions from other pattern-invocations.
531-
//
544+
timer.last_run = 0;
532545
return function () {
533-
clearTimeout(timer.timer);
534546
const args = arguments;
535-
timer.timer = setTimeout(() => func.apply(this, args), ms);
547+
if (!postpone && timer.timer && Date.now() - timer.last_run <= ms) {
548+
return;
549+
}
550+
clearTimeout(timer.timer);
551+
timer.last_run = Date.now();
552+
timer.timer = setTimeout(() => {
553+
func.apply(this, args);
554+
}, ms);
536555
};
537556
};
538557

src/core/utils.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,25 @@ describe("debounce ...", function () {
630630
await utils.timeout(1);
631631
expect(test_func).toHaveBeenCalledTimes(1);
632632
});
633+
it("ensures to be called every x ms", async () => {
634+
const test_func = jest.fn();
635+
const debouncer = utils.debounce(test_func, 2, { timer: null }, false);
636+
debouncer();
637+
await utils.timeout(1);
638+
debouncer();
639+
await utils.timeout(1);
640+
debouncer();
641+
await utils.timeout(1);
642+
debouncer();
643+
await utils.timeout(1);
644+
debouncer();
645+
await utils.timeout(1);
646+
await utils.timeout(1);
647+
648+
// There should be 2 or 3 calls, depending on timing corner cases.
649+
const calls = test_func.mock.calls.length;
650+
expect(calls >= 2 && calls < 4).toBe(true);
651+
});
633652
it("incorrect usage by multi instantiation won't cancel previous runs", async () => {
634653
const test_func = jest.fn();
635654
utils.debounce(test_func, 1)();

0 commit comments

Comments
 (0)