diff --git a/packages/utilities/package.json b/packages/utilities/package.json index 07158c7caf..eb4a7a2def 100644 --- a/packages/utilities/package.json +++ b/packages/utilities/package.json @@ -261,6 +261,11 @@ "import": "./dist/lib/splitText.mjs", "require": "./dist/lib/splitText.js" }, + "./throttle": { + "types": "./dist/lib/throttle.d.ts", + "import": "./dist/lib/throttle.mjs", + "require": "./dist/lib/throttle.js" + }, "./toTitleCase": { "types": "./dist/lib/toTitleCase.d.ts", "import": "./dist/lib/toTitleCase.mjs", diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 5afb457594..e84e3073c4 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -37,6 +37,7 @@ export * from './lib/regExpEsc'; export * from './lib/roundNumber'; export { AbortError, sleep, sleepSync, type SleepOptions } from './lib/sleep'; export * from './lib/splitText'; +export { throttle, type ThrottleFn } from './lib/throttle'; export { toTitleCase, type ToTitleCaseOptions } from './lib/toTitleCase'; export * from './lib/tryParseJSON'; export * from './lib/tryParseURL'; diff --git a/packages/utilities/src/lib/throttle.ts b/packages/utilities/src/lib/throttle.ts new file mode 100644 index 0000000000..dd0655e5a6 --- /dev/null +++ b/packages/utilities/src/lib/throttle.ts @@ -0,0 +1,33 @@ +export type ThrottleFn any> = T & { flush: () => void }; + +/** + * Creates a throttled function that only invokes `func` at most once per + * every `wait` milliseconds. The throttled function comes with a `flush` method to + * reset the last time the throttled function was invoked. + * + * @param func The function to throttle. + * @param wait The number of milliseconds to throttle invocations to. + * + * @returns Returns the new throttled function. + */ +export function throttle any>(func: T, wait: number): ThrottleFn { + let prev = 0; + let prevValue: ReturnType; + + return Object.assign( + (...args: Parameters) => { + const now = Date.now(); + if (now - prev > wait) { + prev = now; + return (prevValue = func(...args)); + } + + return prevValue; + }, + { + flush() { + prev = 0; + } + } + ) as ThrottleFn; +} diff --git a/packages/utilities/tests/throttle.test.ts b/packages/utilities/tests/throttle.test.ts new file mode 100644 index 0000000000..f11bf33181 --- /dev/null +++ b/packages/utilities/tests/throttle.test.ts @@ -0,0 +1,26 @@ +import { throttle, sleep } from '../src'; + +describe('throttle', () => { + test('GIVEN number callback THEN returns the same output until the delay', async () => { + const callback = vi.fn((num: number) => num); + + const throttleFunc = throttle(callback, 50); + const now = Date.now(); + expect(throttleFunc(now)).toEqual(now); + expect(throttleFunc(100)).toEqual(now); + expect(callback).toHaveBeenCalledOnce(); + await sleep(100); + expect(throttleFunc(250)).toEqual(250); + }); + + test('GIVEN number callback THEN returns the new output when flush', () => { + const callback = vi.fn((num: number) => num); + + const throttleFunc = throttle(callback, 100); + const now = Date.now(); + expect(throttleFunc(now)).toEqual(now); + throttleFunc.flush(); + expect(throttleFunc(100)).toEqual(100); + expect(callback).toHaveBeenCalledTimes(2); + }); +});