UNPKG

@tempfix/watcher

Version:

The file system watcher that strives for perfection, with no native dependencies and optional rename detection support.

153 lines (83 loc) 2.97 kB
/* IMPORT */ import type {FN, Debounced} from './types.js'; /* MAIN */ const debounce = <Args extends unknown[]> ( fn: FN<Args, unknown>, wait: number = 1, options?: { leading?: boolean, trailing?: boolean, maxWait?: number } ): Debounced<Args> => { /* VARIABLES */ wait = Math.max ( 1, wait ); const leading = options?.leading ?? false; const trailing = options?.trailing ?? true; const maxWait = Math.max ( options?.maxWait ?? Infinity, wait ); let args: Args | undefined; let timeout: ReturnType<typeof setTimeout> | undefined; let timestampCall = 0; let timestampInvoke = 0; /* HELPERS */ const getInstantData = (): [number, boolean] => { const timestamp = Date.now (); const elapsedCall = timestamp - timestampCall; const elapsedInvoke = timestamp - timestampInvoke; const isInvoke = ( elapsedCall >= wait || elapsedInvoke >= maxWait ); return [timestamp, isInvoke]; }; const invoke = ( timestamp: number ): void => { timestampInvoke = timestamp; if ( !args ) return; // This should never happen const _args = args; args = undefined; fn.apply ( undefined, _args ); }; const onCancel = (): void => { resetTimeout ( 0 ); }; const onFlush = (): void => { if ( !timeout ) return; onCancel (); invoke ( Date.now () ); }; const onLeading = ( timestamp: number ): void => { timestampInvoke = timestamp; if ( leading ) return invoke ( timestamp ); }; const onTrailing = ( timestamp: number ): void => { if ( trailing && args ) return invoke ( timestamp ); args = undefined; }; const onTimeout = (): void => { timeout = undefined; const [timestamp, isInvoking] = getInstantData (); if ( isInvoking ) return onTrailing ( timestamp ); return updateTimeout ( timestamp ); }; const updateTimeout = ( timestamp: number ): void => { const elapsedCall = timestamp - timestampCall; const elapsedInvoke = timestamp - timestampInvoke; const remainingCall = wait - elapsedCall; const remainingInvoke = maxWait - elapsedInvoke; const ms = Math.min ( remainingCall, remainingInvoke ); return resetTimeout ( ms ); }; const resetTimeout = ( ms: number ): void => { if ( timeout ) clearTimeout ( timeout ); if ( ms <= 0 ) return; timeout = setTimeout ( onTimeout, ms ); }; /* DEBOUNCED */ const debounced = ( ...argsLatest: Args ): void => { const [timestamp, isInvoking] = getInstantData (); const hadTimeout = !!timeout; args = argsLatest; timestampCall = timestamp; if ( isInvoking || !timeout ) resetTimeout ( wait ); if ( isInvoking ) { if ( !hadTimeout ) return onLeading ( timestamp ); return invoke ( timestamp ); } }; /* DEBOUNCED UTILITIES */ debounced.cancel = onCancel; debounced.flush = onFlush; /* RETURN */ return debounced; }; /* EXPORT */ export default debounce;