From e616868634090b0a900d0604b0e3bc06ea775af0 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 18 Jan 2021 19:01:06 +0000 Subject: [PATCH 1/7] feat: Add `maxWait` option --- index.d.ts | 9 +++++++++ index.js | 27 +++++++++++++++++++++++++++ test.js | 25 +++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/index.d.ts b/index.d.ts index 69ecbe2..e012932 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,6 +7,15 @@ declare namespace debounceFn { */ readonly wait?: number; + /** + Maximum time to wait until the `input` function is called. + Only applies when after is true. + Disabled when 0 + + @default 0 + */ + readonly maxWait?: number; + /** Trigger the function on the leading edge of the `wait` interval. diff --git a/index.js b/index.js index 9450b4e..059b565 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ module.exports = (inputFunction, options = {}) => { const { wait = 0, + maxWait = 0, before = false, after = true } = options; @@ -17,6 +18,7 @@ module.exports = (inputFunction, options = {}) => { } let timeout; + let maxTimeout; let result; const debouncedFunction = function (...arguments_) { @@ -25,15 +27,35 @@ module.exports = (inputFunction, options = {}) => { const later = () => { timeout = undefined; + if (maxTimeout) { + clearTimeout(maxTimeout); + maxTimeout = undefined; + } + if (after) { result = inputFunction.apply(context, arguments_); } }; + const maxLater = () => { + maxTimeout = undefined; + + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + + result = inputFunction.apply(context, arguments_); + }; + const shouldCallNow = before && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); + if (maxWait > 0 && !maxTimeout && after) { + maxTimeout = setTimeout(maxLater, maxWait); + } + if (shouldCallNow) { result = inputFunction.apply(context, arguments_); } @@ -48,6 +70,11 @@ module.exports = (inputFunction, options = {}) => { clearTimeout(timeout); timeout = undefined; } + + if (maxTimeout) { + clearTimeout(maxTimeout); + maxTimeout = undefined; + } }; return debouncedFunction; diff --git a/test.js b/test.js index c2d00ac..9eb41f4 100644 --- a/test.js +++ b/test.js @@ -142,3 +142,28 @@ test('.cancel() method', async t => { t.is(debounced(3), undefined); t.is(count, 0); }); + +test('debounces a function with maxWait', async t => { + let count = 0; + + const debounced = debounceFn(value => { + count++; + return value; + }, { + wait: 40, + maxWait: 50 + }); + + t.is(debounced(1), undefined); + await delay(30); + t.is(count, 0); + + t.is(debounced(2), undefined); + await delay(30); + t.is(count, 1); + + t.is(debounced(3), 1); + + await delay(200); + t.is(count, 2); +}); From ef525286df9163e424240dfe7634c1121cd3f055 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 23 Jan 2021 14:09:37 +0000 Subject: [PATCH 2/7] chore: review changes --- index.d.ts | 8 +++++--- index.js | 6 ++++-- readme.md | 10 ++++++++++ test.js | 36 ++++++++++++++++++++++++++++++++++-- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/index.d.ts b/index.d.ts index e012932..09c3eed 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,17 +1,19 @@ declare namespace debounceFn { interface Options { /** - Time to wait until the `input` function is called. + Time in milliseconds to wait until the `input` function is called. @default 0 */ readonly wait?: number; /** - Maximum time to wait until the `input` function is called. - Only applies when after is true. + Maximum time in milliseconds to wait between calls to the `input` function. Disabled when 0 + This can be used to limit the number of calls handled in a constant stream. + For example, a media player sending updates every few milliseconds but wants to be handled only once a second. + @default 0 */ readonly maxWait?: number; diff --git a/index.js b/index.js index 059b565..3e0e31c 100644 --- a/index.js +++ b/index.js @@ -45,14 +45,16 @@ module.exports = (inputFunction, options = {}) => { timeout = undefined; } - result = inputFunction.apply(context, arguments_); + if (after) { + result = inputFunction.apply(context, arguments_); + } }; const shouldCallNow = before && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); - if (maxWait > 0 && !maxTimeout && after) { + if (maxWait > 0 && !maxTimeout) { maxTimeout = setTimeout(maxLater, maxWait); } diff --git a/readme.md b/readme.md index 236948d..dc2a358 100644 --- a/readme.md +++ b/readme.md @@ -43,6 +43,16 @@ Default: `0` Time to wait until the `input` function is called. +##### maxWait + +Type: `number`\ +Default: `0` (disabled) + +Maximum time in milliseconds to wait between calls to the `input` function. + +This can be used to limit the number of calls handled in a constant stream. +For example, a media player sending updates every few milliseconds but wants to be handled only once a second. + ##### before Type: `boolean`\ diff --git a/test.js b/test.js index 9eb41f4..902502d 100644 --- a/test.js +++ b/test.js @@ -143,7 +143,7 @@ test('.cancel() method', async t => { t.is(count, 0); }); -test('debounces a function with maxWait', async t => { +test('before:false after:true `maxWait` option', async t => { let count = 0; const debounced = debounceFn(value => { @@ -151,7 +151,9 @@ test('debounces a function with maxWait', async t => { return value; }, { wait: 40, - maxWait: 50 + maxWait: 50, + after: true, + before: false }); t.is(debounced(1), undefined); @@ -167,3 +169,33 @@ test('debounces a function with maxWait', async t => { await delay(200); t.is(count, 2); }); + +test('before:true after:false `maxWait` option', async t => { + let count = 0; + + const debounced = debounceFn(value => { + count++; + return value; + }, { + wait: 40, + maxWait: 50, + after: false, + before: true + }); + + t.is(debounced(1), 1); + t.is(count, 1); + await delay(30); + + t.is(debounced(2), 1); + t.is(count, 1); + await delay(30); + + t.is(debounced(3), 3); + t.is(count, 2); + + await delay(50); + + t.is(debounced(4), 4); + t.is(count, 3); +}); From 0297dc74bb1952ea01581d3ae9709bda5e6f5376 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 27 Feb 2021 00:11:49 +0000 Subject: [PATCH 3/7] chore: review changes --- index.d.ts | 7 +++---- index.js | 4 ++-- test.js | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index 09c3eed..bd1d254 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,13 +8,12 @@ declare namespace debounceFn { readonly wait?: number; /** - Maximum time in milliseconds to wait between calls to the `input` function. - Disabled when 0 + The maximum time the `input` function is allowed to be delayed before it's invoked. - This can be used to limit the number of calls handled in a constant stream. + This can be used to control the rate of calls handled in a constant stream. For example, a media player sending updates every few milliseconds but wants to be handled only once a second. - @default 0 + @default Infinity */ readonly maxWait?: number; diff --git a/index.js b/index.js index 3e0e31c..436e091 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ module.exports = (inputFunction, options = {}) => { const { wait = 0, - maxWait = 0, + maxWait = Number.Infinity, before = false, after = true } = options; @@ -54,7 +54,7 @@ module.exports = (inputFunction, options = {}) => { clearTimeout(timeout); timeout = setTimeout(later, wait); - if (maxWait > 0 && !maxTimeout) { + if (maxWait > 0 && maxWait !== Number.Infinity && !maxTimeout) { maxTimeout = setTimeout(maxLater, maxWait); } diff --git a/test.js b/test.js index 902502d..8418a10 100644 --- a/test.js +++ b/test.js @@ -157,15 +157,18 @@ test('before:false after:true `maxWait` option', async t => { }); t.is(debounced(1), undefined); + t.is(count, 0); await delay(30); t.is(count, 0); t.is(debounced(2), undefined); + t.is(count, 0); await delay(30); t.is(count, 1); t.is(debounced(3), 1); + t.is(count, 1); await delay(200); t.is(count, 2); }); @@ -190,12 +193,46 @@ test('before:true after:false `maxWait` option', async t => { t.is(debounced(2), 1); t.is(count, 1); await delay(30); + t.is(count, 1); t.is(debounced(3), 3); t.is(count, 2); await delay(50); + t.is(count, 2); t.is(debounced(4), 4); t.is(count, 3); }); + +test('before:true after:true `maxWait` option', async t => { + let count = 0; + + const debounced = debounceFn(value => { + count++; + return value; + }, { + wait: 40, + maxWait: 50, + after: true, + before: true + }); + + t.is(debounced(1), 1); + t.is(count, 1); + await delay(30); + + t.is(debounced(2), 1); + t.is(count, 1); + await delay(30); + t.is(count, 2); + + t.is(debounced(3), 3); + t.is(count, 3); + + await delay(50); + t.is(count, 4); + + t.is(debounced(4), 4); + t.is(count, 5); +}); From 502a165ff23232d485493d2f6a02f2db6d48d9b5 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 27 Feb 2021 00:21:34 +0000 Subject: [PATCH 4/7] update readme docs --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 66b67a1..738267a 100644 --- a/readme.md +++ b/readme.md @@ -41,14 +41,14 @@ Type: `object` Type: `number`\ Default: `0` -Time to wait until the `input` function is called. +Time in milliseconds to wait until the `input` function is called. ##### maxWait Type: `number`\ Default: `0` (disabled) -Maximum time in milliseconds to wait between calls to the `input` function. +The maximum time the `input` function is allowed to be delayed before it's invoked. This can be used to limit the number of calls handled in a constant stream. For example, a media player sending updates every few milliseconds but wants to be handled only once a second. From 556641ca0ebd087387ea1232bf670cea7c7e7c53 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 28 Feb 2021 16:30:47 +0700 Subject: [PATCH 5/7] Update index.d.ts --- index.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 3a1ff8c..81b7dfc 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,8 +9,7 @@ export interface Options { /** The maximum time the `input` function is allowed to be delayed before it's invoked. - This can be used to control the rate of calls handled in a constant stream. - For example, a media player sending updates every few milliseconds but wants to be handled only once a second. + This can be used to control the rate of calls handled in a constant stream. For example, a media player sending updates every few milliseconds but wants to be handled only once a second. @default Infinity */ From 3a481b35299a2cdb4299439c210286d2a99fb821 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 28 Feb 2021 16:31:00 +0700 Subject: [PATCH 6/7] Update readme.md --- readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 738267a..9bbd43d 100644 --- a/readme.md +++ b/readme.md @@ -50,8 +50,7 @@ Default: `0` (disabled) The maximum time the `input` function is allowed to be delayed before it's invoked. -This can be used to limit the number of calls handled in a constant stream. -For example, a media player sending updates every few milliseconds but wants to be handled only once a second. +This can be used to limit the number of calls handled in a constant stream. For example, a media player sending updates every few milliseconds but wants to be handled only once a second. ##### before From 8ef37760ec6073ddb7ad8d95cca045f10e1dd30e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 28 Feb 2021 16:32:49 +0700 Subject: [PATCH 7/7] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9bbd43d..e971548 100644 --- a/readme.md +++ b/readme.md @@ -46,7 +46,7 @@ Time in milliseconds to wait until the `input` function is called. ##### maxWait Type: `number`\ -Default: `0` (disabled) +Default: `Infinity` The maximum time the `input` function is allowed to be delayed before it's invoked.