UNPKG

@tienedev/datype

Version:

Modern TypeScript utility library with pragmatic typing and zero dependencies

142 lines (139 loc) 4.62 kB
'use strict'; /** * Creates a throttled function that only invokes `func` at most once per every `wait` milliseconds. * The throttled function comes with a `cancel` method to cancel delayed `func` invocations and * a `flush` method to immediately invoke them. * * @template TArgs - The argument types of the function * @template TReturn - The return type of the function * @param func - The function to throttle * @param wait - The number of milliseconds to throttle invocations to * @param options - Options object to configure throttle behavior * @returns A new throttled function with cancel, flush, and pending methods * * @example * ```typescript * import { throttle } from 'datype'; * * // Basic throttling - limits execution to once per 300ms * const throttledScroll = throttle((event: Event) => { * console.log('Scroll event processed'); * updateScrollPosition(); * }, 300); * * window.addEventListener('scroll', throttledScroll); * * // API rate limiting - ensure function is called at most once per second * const throttledApiCall = throttle(async (data: any) => { * return await fetch('/api/data', { * method: 'POST', * body: JSON.stringify(data) * }); * }, 1000); * * // Leading edge only - execute immediately, then ignore subsequent calls * const buttonClickHandler = throttle(() => { * console.log('Button clicked!'); * }, 1000, { leading: true, trailing: false }); * * // Trailing edge only - wait for quiet period, then execute with latest args * const searchHandler = throttle((query: string) => { * performSearch(query); * }, 300, { leading: false, trailing: true }); * * // Control methods * const throttled = throttle(() => console.log('Hello'), 1000); * throttled.cancel(); // Cancel pending execution * throttled.flush(); // Execute immediately * throttled.pending(); // Check if execution is pending * ``` */ function throttle(func, wait, options = {}) { const { leading = true, trailing = true } = options; let timeoutId; let lastArgs; let lastCallTime; let lastInvokeTime = 0; let result; // Validate inputs if (typeof func !== 'function') { throw new TypeError('Expected a function'); } if (wait < 0) { throw new RangeError('Wait time must be non-negative'); } function invokeFunc(time) { const args = lastArgs; lastArgs = undefined; lastInvokeTime = time; result = func(...args); return result; } function shouldInvoke(time) { const timeSinceLastInvoke = time - lastInvokeTime; // First call or enough time has passed since last invoke return lastCallTime === undefined || timeSinceLastInvoke >= wait; } function leadingEdge(time) { lastInvokeTime = time; // Start the timer for the trailing edge if (trailing) { timeoutId = setTimeout(timerExpired, wait); } return leading ? invokeFunc(time) : result; } function timerExpired() { const time = Date.now(); // Always invoke trailing edge when timer expires trailingEdge(time); } function trailingEdge(time) { timeoutId = undefined; // Only invoke if we have lastArgs which means throttled was called if (trailing && lastArgs !== undefined) { return invokeFunc(time); } lastArgs = undefined; return result; } function cancel() { if (timeoutId !== undefined) { clearTimeout(timeoutId); timeoutId = undefined; } lastInvokeTime = 0; lastArgs = undefined; lastCallTime = undefined; } function flush() { if (timeoutId === undefined) { return result; } const time = Date.now(); return trailingEdge(time); } function pending() { return timeoutId !== undefined; } function throttled(...args) { const time = Date.now(); const isInvoking = shouldInvoke(time); lastArgs = args; lastCallTime = time; if (isInvoking) { if (timeoutId === undefined) { return leadingEdge(time); } } // Start trailing timer if not already running and trailing is enabled if (timeoutId === undefined && trailing) { timeoutId = setTimeout(timerExpired, wait); } return result; } throttled.cancel = cancel; throttled.flush = flush; throttled.pending = pending; return throttled; } exports.throttle = throttle;