All files / src/utils throttle.ts

100% Statements 24/24
90% Branches 9/10
100% Functions 5/5
100% Lines 24/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58                                    6x 6x       6x 14x 14x     6x 18x 18x 9x 9x     9x 9x 6x 5x 5x 5x 5x 5x           6x 1x 1x 1x 1x     6x    
export interface ThrottledFunction<T extends (...args: never[]) => void> {
    (...args: Parameters<T>): void;
    /** Drops the pending call, so that the next call runs at once. */
    cancel(): void;
}
 
/**
 * Calls `fn` at once, then at most once per `wait` milliseconds with the latest arguments, at the
 * end of each interval. es-toolkit's throttle waits `wait` after the last call instead, which
 * slows down updates during continuous pointer moves, such as the split tooltip of highcharts.
 * Time comes from performance.now(), which, unlike Date.now(), does not jump when the system clock
 * is set.
 */
export function throttle<T extends (...args: never[]) => void>(
    fn: T,
    wait: number,
): ThrottledFunction<T> {
    // As in lodash, the interval is converted to a number, so a string such as '100' still works.
    const interval = Number(wait) || 0;
    let lastRun = -Infinity;
    let timer: ReturnType<typeof setTimeout> | undefined;
    let pendingArgs: Parameters<T> | undefined;
 
    const run = (args: Parameters<T>) => {
        lastRun = performance.now();
        fn(...args);
    };
 
    const throttled = (...args: Parameters<T>) => {
        const remaining = lastRun + interval - performance.now();
        if (remaining <= 0 && timer === undefined) {
            run(args);
            return;
        }
 
        pendingArgs = args;
        if (timer === undefined) {
            timer = setTimeout(() => {
                timer = undefined;
                Eif (pendingArgs) {
                    const latestArgs = pendingArgs;
                    pendingArgs = undefined;
                    run(latestArgs);
                }
            }, remaining);
        }
    };
 
    throttled.cancel = () => {
        clearTimeout(timer);
        timer = undefined;
        pendingArgs = undefined;
        lastRun = -Infinity;
    };
 
    return throttled;
}