Advanced function utilities for controlling execution timing, caching results, transforming function behavior, and composing complex operations. Essential for performance optimization and functional programming patterns.
Functions for controlling when and how often functions are executed, crucial for performance optimization in event-heavy applications.
/**
* Creates debounced function that delays execution until after wait time
* @param func - Function to debounce
* @param debounceMs - Delay in milliseconds
* @param options - Configuration options
* @returns Debounced function with control methods
*/
function debounce<F extends (...args: any[]) => any>(
func: F,
debounceMs: number,
options?: DebounceOptions
): DebouncedFunction<F>;
/**
* Creates throttled function that executes at most once per time period
* @param func - Function to throttle
* @param throttleMs - Throttle period in milliseconds
* @param options - Configuration options
* @returns Throttled function with control methods
*/
function throttle<F extends (...args: any[]) => any>(
func: F,
throttleMs: number,
options?: ThrottleOptions
): ThrottledFunction<F>;
interface DebounceOptions {
signal?: AbortSignal;
edges?: Array<'leading' | 'trailing'>;
}
interface ThrottleOptions {
signal?: AbortSignal;
edges?: Array<'leading' | 'trailing'>;
}
interface DebouncedFunction<F extends (...args: any[]) => any> {
(...args: Parameters<F>): void;
schedule(): void;
cancel(): void;
flush(): void;
}
interface ThrottledFunction<F extends (...args: any[]) => any> {
(...args: Parameters<F>): void;
cancel(): void;
flush(): void;
}Usage Examples:
import { debounce, throttle } from 'es-toolkit/function';
// Debounced search input
const searchAPI = (query: string) => {
console.log('Searching for:', query);
// API call here
};
const debouncedSearch = debounce(searchAPI, 300);
// Only the last call within 300ms will execute
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc'); // Only this will execute after 300ms
// Throttled scroll handler
const handleScroll = () => {
console.log('Scroll position:', window.scrollY);
};
const throttledScroll = throttle(handleScroll, 100);
window.addEventListener('scroll', throttledScroll);
// Control methods
const debouncedSave = debounce((data) => console.log('Saving:', data), 1000);
debouncedSave('draft1');
debouncedSave.cancel(); // Cancel pending execution
debouncedSave.flush(); // Execute immediately
// Leading edge execution
const debouncedClick = debounce(handleClick, 300, {
edges: ['leading'] // Execute on first call, then debounce
});
// AbortController integration
const controller = new AbortController();
const debouncedFetch = debounce(fetchData, 500, {
signal: controller.signal
});
// controller.abort() will cancel pending executionCaching function results to improve performance by avoiding repeated expensive computations.
/**
* Creates memoized function that caches results
* @param fn - Function to memoize
* @param options - Memoization options
* @returns Memoized function with cache property
*/
function memoize<F extends (...args: any[]) => any>(
fn: F,
options?: MemoizeOptions<F>
): F & { cache: MemoizeCache<any, ReturnType<F>> };
interface MemoizeOptions<F extends (...args: any[]) => any> {
cache?: MemoizeCache<any, ReturnType<F>>;
getCacheKey?: (args: Parameters<F>[0]) => unknown;
}
interface MemoizeCache<K, V> {
set(key: K, value: V): void;
get(key: K): V | undefined;
has(key: K): boolean;
delete(key: K): boolean | void;
clear(): void;
size: number;
}Usage Examples:
import { memoize } from 'es-toolkit/function';
// Memoize expensive computation
const fibonacci = memoize((n: number): number => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(40)); // First call: computed
console.log(fibonacci(40)); // Second call: cached result
// Custom cache key generation
const fetchUser = memoize(
async (userId: number, includeProfile: boolean) => {
const response = await fetch(`/users/${userId}?profile=${includeProfile}`);
return response.json();
},
{
getCacheKey: (userId, includeProfile) => `${userId}:${includeProfile}`
}
);
// Custom cache implementation
class LRUCache<K, V> implements MemoizeCache<K, V> {
private map = new Map<K, V>();
constructor(private maxSize: number) {}
set(key: K, value: V): void {
if (this.map.size >= this.maxSize && !this.map.has(key)) {
const firstKey = this.map.keys().next().value;
this.map.delete(firstKey);
}
this.map.set(key, value);
}
get(key: K): V | undefined {
const value = this.map.get(key);
if (value !== undefined) {
// Move to end (most recently used)
this.map.delete(key);
this.map.set(key, value);
}
return value;
}
has(key: K): boolean { return this.map.has(key); }
delete(key: K): boolean { return this.map.delete(key); }
clear(): void { this.map.clear(); }
get size(): number { return this.map.size; }
}
const memoizedWithLRU = memoize(expensiveFunction, {
cache: new LRUCache(100)
});
// Cache management
console.log(fibonacci.cache.size); // Number of cached results
fibonacci.cache.clear(); // Clear all cached resultsFunctions for combining multiple functions into single operations, enabling functional programming patterns.
/**
* Composes functions from left to right (first to last)
* @param funcs - Functions to compose
* @returns Single composed function
*/
function flow<T extends ReadonlyArray<(...args: any[]) => any>>(
...funcs: T
): (...args: Parameters<T[0]>) => ReturnType<T[T['length']][number]>;
/**
* Composes functions from right to left (last to first)
* @param funcs - Functions to compose
* @returns Single composed function
*/
function flowRight<T extends ReadonlyArray<(...args: any[]) => any>>(
...funcs: T
): (...args: Parameters<T[T['length']][number]>) => ReturnType<T[0]>;Usage Examples:
import { flow, flowRight } from 'es-toolkit/function';
// Data transformation pipeline
const processText = flow(
(text: string) => text.trim(),
(text: string) => text.toLowerCase(),
(text: string) => text.split(' '),
(words: string[]) => words.filter(word => word.length > 2),
(words: string[]) => words.join('-')
);
console.log(processText(' Hello World Example '));
// Output: 'hello-world-example'
// Mathematical operations
const calculate = flow(
(x: number) => x * 2,
(x: number) => x + 10,
(x: number) => x / 3
);
console.log(calculate(5)); // ((5 * 2) + 10) / 3 = 6.67
// Reverse composition with flowRight
const reverseProcess = flowRight(
(words: string[]) => words.join('-'),
(words: string[]) => words.filter(word => word.length > 2),
(text: string) => text.split(' '),
(text: string) => text.toLowerCase(),
(text: string) => text.trim()
);
// Same result as processText but defined in reverse order
console.log(reverseProcess(' Hello World Example '));Functions for transforming multi-parameter functions into sequences of single-parameter functions.
/**
* Creates curried version of function (left to right)
* @param func - Function to curry
* @returns Curried function
*/
function curry<P extends ReadonlyArray<any>, R>(
func: (...args: P) => R
): CurriedFunction<P, R>;
/**
* Creates curried version of function (right to left)
* @param func - Function to curry
* @returns Right-curried function
*/
function curryRight<P extends ReadonlyArray<any>, R>(
func: (...args: P) => R
): CurriedFunction<P, R>;
type CurriedFunction<P extends ReadonlyArray<any>, R> =
P extends readonly [infer A, ...infer Rest]
? (arg: A) => Rest extends readonly []
? R
: CurriedFunction<Rest, R>
: () => R;Usage Examples:
import { curry, curryRight } from 'es-toolkit/function';
// Basic currying
const add = (a: number, b: number, c: number) => a + b + c;
const curriedAdd = curry(add);
// Can be called in multiple ways
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
// Partial application for reusable functions
const addTen = curriedAdd(10);
const addTenAndFive = addTen(5);
console.log(addTenAndFive(3)); // 18
// Practical example: filtering arrays
const filter = curry(<T>(predicate: (item: T) => boolean, array: T[]) =>
array.filter(predicate)
);
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const isEven = (n: number) => n % 2 === 0;
const isGreaterThan5 = (n: number) => n > 5;
const filterEven = filter(isEven);
const filterGreaterThan5 = filter(isGreaterThan5);
console.log(filterEven(numbers)); // [2, 4, 6, 8, 10]
console.log(filterGreaterThan5(numbers)); // [6, 7, 8, 9, 10]
// Right currying
const divide = (a: number, b: number) => a / b;
const rightCurriedDivide = curryRight(divide);
const divideBy2 = rightCurriedDivide(2); // (a) => a / 2
console.log(divideBy2(10)); // 5Functions for creating new functions with some arguments pre-filled.
/**
* Creates function with some arguments pre-filled from left
* @param func - Function to partially apply
* @param partialArgs - Arguments to pre-fill
* @returns Partially applied function
*/
function partial<T extends (...args: any[]) => any>(
func: T,
...partialArgs: any[]
): T;
/**
* Creates function with some arguments pre-filled from right
* @param func - Function to partially apply
* @param partialArgs - Arguments to pre-fill from right
* @returns Partially applied function
*/
function partialRight<T extends (...args: any[]) => any>(
func: T,
...partialArgs: any[]
): T;Usage Examples:
import { partial, partialRight } from 'es-toolkit/function';
// Left partial application
const greet = (greeting: string, name: string, punctuation: string) =>
`${greeting} ${name}${punctuation}`;
const sayHello = partial(greet, 'Hello');
console.log(sayHello('Alice', '!')); // 'Hello Alice!'
const sayHelloAlice = partial(greet, 'Hello', 'Alice');
console.log(sayHelloAlice('.')); // 'Hello Alice.'
// Right partial application
const exclaim = partialRight(greet, '!');
console.log(exclaim('Hi', 'Bob')); // 'Hi Bob!'
// Practical example: event handlers
const logEvent = (level: string, category: string, message: string) => {
console.log(`[${level}] ${category}: ${message}`);
};
const logError = partial(logEvent, 'ERROR');
const logWarning = partial(logEvent, 'WARNING');
const logUserError = partial(logEvent, 'ERROR', 'USER');
logError('AUTH', 'Invalid credentials');
logWarning('PERF', 'Slow query detected');
logUserError('Invalid input provided');Utilities for modifying function behavior including arity control and execution constraints.
/**
* Creates function that accepts only n arguments
* @param func - Function to cap
* @param n - Maximum number of arguments
* @returns Function accepting only n arguments
*/
function ary<T extends (...args: any[]) => any>(func: T, n: number): T;
/**
* Creates function that accepts only 1 argument
* @param func - Function to make unary
* @returns Unary function
*/
function unary<T extends (...args: any[]) => any>(func: T): T;
/**
* Creates negated version of predicate function
* @param predicate - Predicate function to negate
* @returns Negated predicate function
*/
function negate<T extends (...args: any[]) => boolean>(predicate: T): T;
/**
* Creates function that can only be called once
* @param func - Function to execute once
* @returns Function that executes only once
*/
function once<T extends (...args: any[]) => any>(func: T): T;
/**
* Creates function that can only be called before n times
* @param func - Function to limit
* @param n - Maximum number of calls
* @returns Function that stops executing after n-1 calls
*/
function before<T extends (...args: any[]) => any>(func: T, n: number): T;
/**
* Creates function that can only be called after n calls
* @param func - Function to execute
* @param n - Number of calls before execution starts
* @returns Function that executes only after n calls
*/
function after<T extends (...args: any[]) => any>(func: T, n: number): T;Usage Examples:
import { ary, unary, negate, once, before, after } from 'es-toolkit/function';
// Arity control
const sum = (a: number, b: number, c: number) => a + b + c;
const binarySum = ary(sum, 2); // Only uses first 2 arguments
console.log(binarySum(1, 2, 999)); // 3 (ignores 999)
// Unary function for map
const numbers = ['1', '2', '3'];
console.log(numbers.map(unary(parseInt))); // [1, 2, 3]
// Without unary: parseInt gets index as second argument, causing issues
// Negate predicate
const isEven = (n: number) => n % 2 === 0;
const isOdd = negate(isEven);
const nums = [1, 2, 3, 4, 5];
console.log(nums.filter(isEven)); // [2, 4]
console.log(nums.filter(isOdd)); // [1, 3, 5]
// Execute once only
const initialize = once(() => {
console.log('Initializing...');
return 'initialized';
});
initialize(); // Logs: "Initializing..."
initialize(); // Silent
initialize(); // Silent
// Before/after constraints
const setupLogger = after((message: string) => {
console.log('Setup complete:', message);
}, 3);
setupLogger('attempt 1'); // Silent
setupLogger('attempt 2'); // Silent
setupLogger('attempt 3'); // Logs: "Setup complete: attempt 3"
const warningLogger = before((message: string) => {
console.log('Warning:', message);
}, 3);
warningLogger('issue 1'); // Logs: "Warning: issue 1"
warningLogger('issue 2'); // Logs: "Warning: issue 2"
warningLogger('issue 3'); // Silent (3rd call stops execution)Basic utility functions for common patterns and no-operation scenarios.
/**
* Returns the value unchanged
* @param value - Value to return
* @returns Same value
*/
function identity<T>(value: T): T;
/**
* No-operation function
* @returns undefined
*/
function noop(): void;
/**
* Async no-operation function
* @returns Promise resolving to undefined
*/
function asyncNoop(): Promise<void>;
/**
* Retries function execution with delay between attempts
* @param func - Function to retry
* @param retries - Number of retry attempts
* @param delay - Delay between retries in milliseconds
* @returns Promise resolving to function result
*/
function retry<T>(
func: () => T | Promise<T>,
retries: number,
delay?: number
): Promise<T>;Usage Examples:
import { identity, noop, asyncNoop, retry } from 'es-toolkit/function';
// Identity function for filtering/mapping
const mixedArray = [0, 1, false, true, '', 'hello', null, 'world'];
const truthyValues = mixedArray.filter(identity);
console.log(truthyValues); // [1, true, 'hello', 'world']
// No-op for default callbacks
function processData(data: any[], callback = noop) {
// Process data...
callback();
}
// Retry with backoff
const fetchData = async () => {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Fetch failed');
return response.json();
};
const dataWithRetry = await retry(fetchData, 3, 1000);
// Will retry up to 3 times with 1 second delay between attemptsimport { flow, retry, memoize } from 'es-toolkit/function';
const processWithRetry = flow(
(data: string) => data.trim(),
(data: string) => JSON.parse(data),
memoize((data: object) => expensiveTransform(data)),
(data: any) => retry(() => saveToDatabase(data), 3, 1000)
);import { debounce, throttle, once } from 'es-toolkit/function';
// Combine multiple timing controls
const optimizedHandler = debounce(
throttle(
once(expensiveEventHandler),
100
),
300
);