diff --git a/lib/async.js b/lib/async.js index 7ff9f2d..0bd47d1 100644 --- a/lib/async.js +++ b/lib/async.js @@ -46,4 +46,42 @@ const timeoutify = (promise, msec) => ); }); -module.exports = { toBool, timeout, delay, timeoutify }; +const throttle = (timeout, fn, ...args) => { + let timer; + let wait = false; + + const execute = args + ? (...pars) => (pars ? fn(...args, ...pars) : fn(...args)) + : (...pars) => (pars ? fn(...pars) : fn()); + + const delayed = (...pars) => { + timer = undefined; + if (wait) execute(...pars); + }; + + const throttled = (...pars) => { + if (!timer) { + timer = setTimeout(delayed, timeout, ...pars); + wait = false; + execute(...pars); + } + wait = true; + }; + + return throttled; +}; + +const debounce = (timeout, fn, ...args) => { + let timer; + + const debounced = () => (args ? fn(...args) : fn()); + + const wrapped = () => { + if (timer) clearTimeout(timer); + timer = setTimeout(debounced, timeout); + }; + + return wrapped; +}; + +module.exports = { toBool, timeout, delay, timeoutify, throttle, debounce }; diff --git a/test/async.js b/test/async.js index 43f766d..272c314 100644 --- a/test/async.js +++ b/test/async.js @@ -2,7 +2,14 @@ const test = require('node:test'); const assert = require('node:assert'); -const { toBool, timeout, delay, timeoutify } = require('..'); +const { + toBool, + timeout, + delay, + timeoutify, + throttle, + debounce, +} = require('..'); test('Async: toBool', async () => { const success = await Promise.resolve('success').then(...toBool); @@ -67,3 +74,96 @@ test('Async: timeoutify', async () => { assert.ifError(new Error('Should not be executed')); } }); + +test('Async: throttle', () => { + let callCount = 0; + + const fn = (arg1, arg2, ...otherArgs) => { + assert.strictEqual(arg1, 'someVal'); + assert.strictEqual(arg2, 4); + assert.strictEqual(otherArgs, []); + callCount++; + }; + + const throttledFn = throttle(1, fn, 'someVal', 4); + + throttledFn(); + assert.strictEqual(callCount, 1); + throttledFn(); + throttledFn(); + assert.strictEqual(callCount, 1); +}); + +test('Async: throttle merge args', () => { + let callCount = 0; + + const fn = (arg1, arg2, ...otherArgs) => { + assert.strictEqual(arg1, 'someVal'); + assert.strictEqual(arg2, 4); + assert.strictEqual(otherArgs, ['str']); + callCount++; + }; + + const throttledFn = throttle(1, fn, 'someVal', 4); + + throttledFn('str'); + assert.strictEqual(callCount, 1); + throttledFn('str'); + throttledFn('str'); + assert.strictEqual(callCount, 1); +}); + +test('Async: throttle without arguments', () => { + let callCount = 0; + + const fn = (...args) => { + assert.strictEqual(args, []); + callCount++; + }; + + const throttledFn = throttle(1, fn); + + throttledFn(); + assert.strictEqual(callCount, 1); + throttledFn(); + throttledFn(); + assert.strictEqual(callCount, 1); +}); + +test('Async: debounce', async () => { + const { promise, resolve } = Promise.withResolvers(); + let count = 0; + + const fn = (arg1, arg2, ...otherArgs) => { + assert.strictEqual(arg1, 'someVal'); + assert.strictEqual(arg2, 4); + assert.strictEqual(otherArgs, []); + count++; + resolve(); + }; + + const debouncedFn = debounce(1, fn, 'someVal', 4); + + debouncedFn(); + debouncedFn(); + assert.strictEqual(count, 0); + return promise; +}); + +test('Async: debounce without arguments', async () => { + const { promise, resolve } = Promise.withResolvers(); + let count = 0; + + const fn = (...args) => { + assert.strictEqual(args, []); + count++; + resolve(); + }; + + const debouncedFn = debounce(1, fn); + + debouncedFn(); + debouncedFn(); + assert.strictEqual(count, 0); + return promise; +});