once
Ensures a function is only called once. Provides a .reset()
method to allow re-invocation.
1/**
2 * Ensures a function is only called once.
3 * Provides a `.reset()` method to allow re-invocation.
4 *
5 * @param fn - The function to wrap.
6 * @returns A function that can only be called once, with `.reset()` method.
7 */
8export function once<T extends (...args: any[]) => any>(
9 fn: T
10): ((...args: Parameters<T>) => ReturnType<T>) & { reset: () => void } {
11 let called = false;
12 let result: ReturnType<T>;
13
14 const wrapper = (...args: Parameters<T>) => {
15 if (!called) {
16 result = fn(...args);
17 called = true;
18 }
19 return result;
20 };
21
22 wrapper.reset = () => {
23 called = false;
24 result = undefined as any;
25 };
26
27 return wrapper;
28}
Ensures Single Execution
Guarantees that the wrapped function runs only once, making it ideal for one-time initialization logic.
Result Memoization
Returns the cached result on subsequent calls, preserving consistent output without recomputation.
Reset Capability
Includes a
.reset()
method to explicitly allow the function to run again, offering manual control over reuse.Type-Safe and Generalized
Preserves argument and return types through TypeScript generics, making it safe for a variety of function signatures.
Lightweight and Side-Effect-Free
Cleanly separates function invocation logic without modifying the original function itself.
Tests | Examples
1test('once - only calls function once', () => {
2 const fn = jest.fn((x: number) => x * 2);
3 const wrapped = once(fn);
4
5 expect(wrapped(2)).toBe(4);
6 expect(wrapped(3)).toBe(4); // returns first result
7 expect(wrapped(10)).toBe(4);
8 expect(fn).toHaveBeenCalledTimes(1);
9});
10
11test('once - no arguments', () => {
12 const fn = jest.fn(() => 'ran');
13 const wrapped = once(fn);
14
15 expect(wrapped()).toBe('ran');
16 expect(wrapped()).toBe('ran');
17 expect(fn).toHaveBeenCalledTimes(1);
18});
19
20test('once - reset allows re-invocation', () => {
21 const fn = jest.fn((x: number) => x + 1);
22 const wrapped = once(fn);
23
24 expect(wrapped(1)).toBe(2);
25 expect(wrapped(2)).toBe(2);
26 expect(fn).toHaveBeenCalledTimes(1);
27
28 wrapped.reset();
29
30 expect(wrapped(5)).toBe(6);
31 expect(fn).toHaveBeenCalledTimes(2);
32});
Common Use Cases
Initialization Logic
Ensure startup code (e.g., setting up listeners, opening connections) runs only once, even if invoked multiple times.
Lazy Loading or Memoization
Run heavy computations or resource-loading code just once per lifecycle or session.
Single-Use API Calls
Prevent duplicate submissions of forms or repeated triggering of critical actions (e.g., payment processing).
Testing and Setup Scripts
Guarantee setup or teardown functions only run once per test suite or script run.
Lifecycle Management in UI
Execute logic like animation setup, component hydration, or event binding a single time with the option to reset later.