Skip to content

Commit

Permalink
feat(essentials): add debounce
Browse files Browse the repository at this point in the history
  • Loading branch information
thijsdaniels committed Jun 7, 2024
1 parent d1d2171 commit 451e83c
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-tables-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@codedazur/essentials": minor
---

The debounce utility was added.
5 changes: 3 additions & 2 deletions packages/essentials/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./types/optional";
export * from "./utilities/array/shuffle";
export * from "./utilities/assert";
export * from "./utilities/geometry/Angle";
export * from "./utilities/geometry/Direction";
export * from "./utilities/geometry/Origin";
Expand All @@ -20,6 +21,6 @@ export * from "./utilities/string/camelCase";
export * from "./utilities/string/pascalCase";
export * from "./utilities/string/timecode";
export * from "./utilities/system/env";
export * from "./utilities/timing/sleep";
export * from "./utilities/timing/Timer";
export * from "./utilities/assert";
export * from "./utilities/timing/debounce";
export * from "./utilities/timing/sleep";
72 changes: 72 additions & 0 deletions packages/essentials/utilities/timing/debounce.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
afterAll,
afterEach,
beforeAll,
describe,
expect,
it,
vi,
} from "vitest";
import { debounce } from "./debounce";

beforeAll(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.clearAllTimers();
});

afterAll(() => {
vi.useRealTimers();
});

describe("debounce", () => {
it("should debounce the function", async () => {
const callback = vi.fn();
const [debounced] = debounce(callback, 100);

debounced();
debounced();

expect(callback).not.toHaveBeenCalled();

await vi.advanceTimersByTimeAsync(50);
expect(callback).not.toHaveBeenCalled();

await vi.advanceTimersByTimeAsync(50);
expect(callback).toHaveBeenCalledOnce();
});

it("should support canceling the debounced function", async () => {
const callback = vi.fn();
const [debounced, cancel] = debounce(callback, 100);

debounced();
cancel();

await vi.advanceTimersByTimeAsync(100);
expect(callback).not.toHaveBeenCalled();
});

it("should support arguments", async () => {
const callback = vi.fn();
const [debounced] = debounce(callback, 100);

debounced(1, 2, 3);

await vi.advanceTimersByTimeAsync(100);
expect(callback).toHaveBeenCalledWith(1, 2, 3);
});

it("should support return values", async () => {
const [debounced] = debounce(() => 42, 100);

let result;
debounced().then((value) => (result = value));
expect(result).toBe(undefined);

await vi.advanceTimersByTimeAsync(100);
expect(result).toBe(42);
});
});
27 changes: 27 additions & 0 deletions packages/essentials/utilities/timing/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type DebouncedFunction<F extends (...args: never[]) => ReturnType<F>> = (
...args: Parameters<F>
) => Promise<ReturnType<F>>;

type CancelFunction = () => void;

export function debounce<F extends (...args: never[]) => ReturnType<F>>(
callback: F,
ms = 50,
): [DebouncedFunction<F>, CancelFunction] {
let timer: NodeJS.Timeout | undefined;

return [
(...args: Parameters<F>) =>
new Promise((resolve) => {
if (timer) {
clearTimeout(timer);
}

timer = setTimeout(() => {
timer = undefined;
resolve(callback(...args));
}, ms);
}),
() => clearTimeout(timer),
];
}

0 comments on commit 451e83c

Please sign in to comment.