UNPKG

@tienedev/datype

Version:

Modern TypeScript utility library with pragmatic typing and zero dependencies

190 lines (187 loc) 6.43 kB
'use strict'; /** * Creates a debounced function that delays invoking `func` until after `wait` milliseconds * have elapsed since the last time the debounced function was invoked. * * @template TArgs - The argument types of the function * @template TReturn - The return type of the function * @param func - The function to debounce * @param wait - The number of milliseconds to delay * @param options - Options object to configure debounce behavior * @returns A new debounced function with cancel, flush, and pending methods * * @example * ```typescript * import { debounce } from 'datype'; * * // Basic debouncing - delays execution until 300ms after last call * const debouncedSearch = debounce((query: string) => { * console.log('Searching for:', query); * return fetchSearchResults(query); * }, 300); * * // Only the last call will execute after 300ms * debouncedSearch('a'); * debouncedSearch('ab'); * debouncedSearch('abc'); // Only this will execute * * // Leading edge execution - executes immediately on first call * const leadingDebounce = debounce(() => { * console.log('Button clicked!'); * }, 1000, { leading: true, trailing: false }); * * // Control methods * const debounced = debounce(() => console.log('Hello'), 1000); * debounced.cancel(); // Cancel pending execution * debounced.flush(); // Execute immediately * debounced.pending(); // Check if execution is pending * * // Maximum wait time - ensures function is called at most once per maxWait * const maxWaitDebounce = debounce(updateUI, 100, { maxWait: 1000 }); * ``` */ function debounce(func, wait, options = {}) { const { leading = false, trailing = true, maxWait } = options; let timeoutId; let maxTimeoutId; let lastArgs; let lastCallTime; let lastInvokeTime = 0; let result; if (typeof func !== 'function') { throw new TypeError('Expected a function'); } if (wait < 0) { throw new RangeError('Wait time must be non-negative'); } if (maxWait !== undefined && maxWait < wait) { throw new RangeError('maxWait must be greater than or equal to wait'); } function invokeFunc(time) { const args = lastArgs; lastArgs = undefined; lastInvokeTime = time; result = func(...args); return result; } function shouldInvoke(time) { const timeSinceLastCall = time - (lastCallTime || 0); const timeSinceLastInvoke = time - lastInvokeTime; return (lastCallTime === undefined || timeSinceLastCall >= wait || (maxWait !== undefined && timeSinceLastInvoke >= maxWait)); } function leadingEdge(time) { lastInvokeTime = time; timeoutId = setTimeout(timerExpired, wait); if (maxWait !== undefined) { maxTimeoutId = setTimeout(maxTimerExpired, maxWait); } return leading ? invokeFunc(time) : result; } function remainingWait(time) { const timeSinceLastCall = time - (lastCallTime || 0); const timeSinceLastInvoke = time - lastInvokeTime; const timeWaiting = wait - timeSinceLastCall; return maxWait !== undefined ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting; } function timerExpired() { const time = Date.now(); if (shouldInvoke(time)) { trailingEdge(time); } else { // Restart the timer timeoutId = setTimeout(timerExpired, remainingWait(time)); } } function maxTimerExpired() { const time = Date.now(); if (lastArgs !== undefined) { // Clear the regular timer when maxWait fires if (timeoutId !== undefined) { clearTimeout(timeoutId); timeoutId = undefined; } trailingEdge(time); } } function trailingEdge(time) { timeoutId = undefined; maxTimeoutId = undefined; // Only invoke if we have lastArgs which means debounced was called if (trailing && lastArgs !== undefined) { return invokeFunc(time); } lastArgs = undefined; return result; } function cancel() { if (timeoutId !== undefined) { clearTimeout(timeoutId); timeoutId = undefined; } if (maxTimeoutId !== undefined) { clearTimeout(maxTimeoutId); maxTimeoutId = undefined; } lastInvokeTime = 0; lastArgs = undefined; lastCallTime = undefined; } function flush() { if (timeoutId === undefined && maxTimeoutId === undefined) { return result; } // Clear all timers and invoke with current args if any if (timeoutId !== undefined) { clearTimeout(timeoutId); timeoutId = undefined; } if (maxTimeoutId !== undefined) { clearTimeout(maxTimeoutId); maxTimeoutId = undefined; } const time = Date.now(); if (lastArgs !== undefined) { return invokeFunc(time); } return result; } function pending() { return timeoutId !== undefined || maxTimeoutId !== undefined; } function debounced(...args) { const time = Date.now(); const isInvoking = shouldInvoke(time); lastArgs = args; lastCallTime = time; if (isInvoking) { if (timeoutId === undefined && maxTimeoutId === undefined) { return leadingEdge(time); } if (maxTimeoutId !== undefined) { // Handle maxWait timeout - clear existing timers and reset clearTimeout(timeoutId); clearTimeout(maxTimeoutId); timeoutId = undefined; maxTimeoutId = undefined; return invokeFunc(time); } } if (timeoutId === undefined) { timeoutId = setTimeout(timerExpired, wait); } if (maxWait !== undefined && maxTimeoutId === undefined) { maxTimeoutId = setTimeout(maxTimerExpired, maxWait); } return result; } debounced.cancel = cancel; debounced.flush = flush; debounced.pending = pending; return debounced; } exports.debounce = debounce;