Function manipulation and execution utilities providing batching, control flow helpers, throttling, debouncing, and functional programming patterns.
Utilities for controlling function execution patterns and batching operations.
/**
* Call every function in an array
* @param functions - Array of functions to call (null/undefined values are skipped)
*/
function batchInvoke(functions: Nullable<Fn>[]): void;
/**
* Call the function, returning the result
* @param fn - Function to invoke
* @returns Result of the function call
*/
function invoke<T>(fn: () => T): T;Usage Examples:
import { batchInvoke, invoke } from "@antfu/utils";
// Batch function execution
const cleanupFunctions = [
() => clearInterval(timer1),
() => clearTimeout(timeout1),
null, // Safe to include null values
() => removeEventListener('click', handler),
undefined, // Safe to include undefined values
() => database.disconnect()
];
// Execute all cleanup functions
batchInvoke(cleanupFunctions);
// Event handler management
class EventManager {
private handlers: Array<(() => void) | null> = [];
addHandler(handler: () => void): void {
this.handlers.push(handler);
}
removeHandler(handler: () => void): void {
const index = this.handlers.indexOf(handler);
if (index !== -1) {
this.handlers[index] = null; // Mark as removed
}
}
triggerAll(): void {
batchInvoke(this.handlers);
this.handlers = this.handlers.filter(h => h !== null); // Cleanup
}
}
// Lazy evaluation with invoke
const expensiveCalculation = () => {
console.log('Performing expensive calculation...');
return Math.random() * 1000;
};
// Only calculate when needed
function processData(useExpensive: boolean) {
const baseValue = 42;
const expensiveValue = useExpensive ? invoke(expensiveCalculation) : 0;
return baseValue + expensiveValue;
}
// Plugin system
const plugins = [
() => initializePlugin1(),
() => initializePlugin2(),
null, // Disabled plugin
() => initializePlugin3()
];
// Initialize all active plugins
batchInvoke(plugins);Function composition and data transformation utilities.
/**
* Pass the value through the callback, and return the value
* @param value - Value to pass through
* @param callback - Function to call with the value
* @returns The original value (unchanged)
*/
function tap<T>(value: T, callback: (value: T) => void): T;Usage Examples:
import { tap } from "@antfu/utils";
// Debugging in function chains
const result = [1, 2, 3, 4, 5]
.filter(n => n % 2 === 0)
.map(n => n * 2)
.tap(arr => console.log('After doubling:', arr)) // Debug without breaking chain
.reduce((sum, n) => sum + n, 0);
// Object initialization
function createUser(name: string): User {
return tap(new User(), user => {
user.name = name;
user.createdAt = new Date();
user.id = generateId();
});
}
// Configuration with side effects
const config = tap(loadConfig(), config => {
validateConfig(config);
logConfigLoad(config);
sendTelemetry('config-loaded', config.version);
});
// DOM manipulation
const element = tap(document.createElement('div'), div => {
div.className = 'my-component';
div.setAttribute('data-id', '123');
div.addEventListener('click', handleClick);
});
// API response processing
async function fetchUserData(userId: string) {
return fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(user => tap(user, user => {
// Log user access without affecting the data flow
analytics.track('user-data-accessed', { userId: user.id });
cache.set(`user:${userId}`, user);
}));
}
// Conditional side effects
function processOrder(order: Order): Order {
return tap(order, order => {
if (order.total > 1000) {
notifyManager('large-order', order);
}
if (order.isUrgent) {
priorityQueue.add(order);
}
});
}
// Fluent API building
class QueryBuilder {
private query = '';
select(fields: string): this {
return tap(this, () => {
this.query += `SELECT ${fields} `;
});
}
from(table: string): this {
return tap(this, () => {
this.query += `FROM ${table} `;
});
}
where(condition: string): this {
return tap(this, () => {
this.query += `WHERE ${condition} `;
});
}
build(): string {
return this.query.trim();
}
}
// Usage: const sql = new QueryBuilder().select('*').from('users').where('active = 1').build();Advanced function rate limiting with cancellation support.
interface CancelOptions {
upcomingOnly?: boolean;
}
interface ReturnWithCancel<T extends (...args: any[]) => any> {
(...args: Parameters<T>): void;
cancel: (options?: CancelOptions) => void;
}
/**
* Throttle function execution - limits calls to at most once per specified time period
* @param delay - Delay in milliseconds
* @param callback - Function to throttle
* @param options - Optional configuration
* @returns Throttled function with cancel method
*/
function throttle<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: { noTrailing?: boolean; noLeading?: boolean; debounceMode?: boolean }
): ReturnWithCancel<T>;
/**
* Debounce function execution - delays execution until after specified time has passed since last call
* @param delay - Delay in milliseconds
* @param callback - Function to debounce
* @param options - Optional configuration
* @returns Debounced function with cancel method
*/
function debounce<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: { atBegin?: boolean }
): ReturnWithCancel<T>;Usage Examples:
import { throttle, debounce } from "@antfu/utils";
// Throttled scroll handler (executes at most once every 100ms)
const handleScroll = throttle(100, () => {
console.log('Scroll position:', window.scrollY);
updateScrollIndicator();
});
window.addEventListener('scroll', handleScroll);
// Debounced search (waits 300ms after user stops typing)
const performSearch = debounce(300, (query: string) => {
console.log('Searching for:', query);
searchAPI(query);
});
const searchInput = document.getElementById('search') as HTMLInputElement;
searchInput.addEventListener('input', (e) => {
performSearch((e.target as HTMLInputElement).value);
});
// Throttled API calls for real-time data
const updateMetrics = throttle(5000, async () => {
const metrics = await fetchLatestMetrics();
updateDashboard(metrics);
});
// Auto-save with debouncing
const autoSave = debounce(2000, (data: any) => {
localStorage.setItem('draft', JSON.stringify(data));
console.log('Draft saved automatically');
});
// Window resize handling
const handleResize = throttle(150, () => {
updateLayout();
recalculatePositions();
});
window.addEventListener('resize', handleResize);
// Button click protection (prevent double-clicks)
const submitForm = debounce(1000, async (formData: FormData) => {
try {
await api.submitForm(formData);
showSuccessMessage();
} catch (error) {
showErrorMessage(error);
}
});
// Cancellation examples
const debouncedSave = debounce(1000, saveData);
// Cancel pending saves before navigation
function beforeNavigate() {
debouncedSave.cancel(); // Cancel any pending execution
}
// Cancel only upcoming calls, let current one complete
function softCancel() {
debouncedSave.cancel({ upcomingOnly: true });
}
// Mouse tracking with throttling
const trackMouse = throttle(50, (x: number, y: number) => {
updateCursor(x, y);
sendMouseData({ x, y, timestamp: Date.now() });
});
document.addEventListener('mousemove', (e) => {
trackMouse(e.clientX, e.clientY);
});
// Real-world example: Search suggestions
class SearchWidget {
private suggestionsDebounced = debounce(300, this.loadSuggestions.bind(this));
private metricsThrottled = throttle(1000, this.trackSearchMetrics.bind(this));
onInput(query: string): void {
this.suggestionsDebounced(query);
this.metricsThrottled(query);
}
private async loadSuggestions(query: string): Promise<void> {
if (query.length < 2) return;
const suggestions = await this.api.getSuggestions(query);
this.displaySuggestions(suggestions);
}
private trackSearchMetrics(query: string): void {
this.analytics.track('search-input', { query, length: query.length });
}
destroy(): void {
// Clean up any pending calls
this.suggestionsDebounced.cancel();
this.metricsThrottled.cancel();
}
}