Skip to content

Instantly share code, notes, and snippets.

@zenyr
Last active October 9, 2022 18:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zenyr/8eeb3311df7e2f9fdf7588bf7bf2ddb8 to your computer and use it in GitHub Desktop.
Save zenyr/8eeb3311df7e2f9fdf7588bf7bf2ddb8 to your computer and use it in GitHub Desktop.
Typescript: Throttle with trailing call v.2022

Why?

  • I needed to have a lightweight throttle function that I can rely on.
  • Calls ASAP
  • Defers call if throttled
  • Can cancel an enqueued call if you really want to (say, a component has unmounted, instance was destroyed etc.)
  • Something TSC would infer type safely

Code

/**
 * Throttles function & keep the last call.
 *
 * @param thunk a function that should be throttled
 * @param delay ms
 * @returns function
 */
export const throttle = <T>(thunk: T, delay: number): T & { cancel(): void } => {
  if (typeof thunk !== 'function') throw new Error('thunk must be a function');
  let ok = true;
  let timer: number;
  const pending = { current: null as unknown[] | null };

  const throttled: T = ((...args: unknown[]) => {
    if (!ok) {
      // timer is ticking, update arguments
      pending.current = args;
    } else {
      ok = false;
      thunk(...args);
      pending.current = null;
      timer && self.clearTimeout(timer);
      timer = self.setTimeout(() => {
        ok = true;
        if (pending.current) {
          // arguments queued!
          if (typeof throttled !== 'function') throw new Error('throttled must be a function');
          throttled(...pending.current);
        }
      }, delay);
    }
  }) as unknown as T;
  (throttled as T & { cancel(): void }).cancel = () => {
    timer && clearTimeout(timer);
    pending.current = null;
  };

  // ts cannot infer the return type correctly
  return throttled as unknown as T & { cancel(): void };
};

Test

 const run = throttle((msg: string) => console.log(msg), 1000);
  run('hej1'); // immediately fired
  await sleep(200);
  run('hej2'); // discarded (superseded by 3)
  await sleep(200);
  run('hej3'); // fired after 1s
  await sleep(500);
  run('hej4'); // discarded
  await sleep(500);
  run('hej5'); // fired after 2s (unless you don't cancel it)
  run.cancel(); // << comment this to prevent '5' firing on the next tick
  console.log('>>>test---');

License

  • WTFPL
  • Do wtf you wish to do
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment