Throttle Calls With Leading and Trailing

Limit function execution frequency with configurable leading and trailing edges, plus cancel and flush controls for cleanup.

JavascriptResilience

Code

snippet
export function throttle(fn, wait, options = {}) {
  if (typeof fn !== "function") throw new TypeError("fn must be a function");
  if (!Number.isFinite(wait) || wait < 0) throw new RangeError("wait must be a non-negative number");
  const { leading = true, trailing = true } = options;
  let timer = null;
  let lastInvoke = 0;
  let pendingArgs = null;
  let pendingThis = null;
  let started = false;

  function invoke() {
    const args = pendingArgs;
    const context = pendingThis;
    pendingArgs = null;
    pendingThis = null;
    if (args === null) return;
    lastInvoke = Date.now();
    fn.apply(context, args);
  }

  function schedule(delay) {
    if (timer !== null) return;
    timer = setTimeout(() => {
      timer = null;
      if (trailing) invoke();
    }, delay);
  }

  function throttled(...args) {
    const now = Date.now();
    if (!started) {
      started = true;
      lastInvoke = now;
      if (leading) {
        fn.apply(this, args);
      } else {
        pendingArgs = args;
        pendingThis = this;
        schedule(wait);
      }
      return;
    }
    const elapsed = now - lastInvoke;
    if (elapsed >= wait) {
      if (timer !== null) {
        clearTimeout(timer);
        timer = null;
      }
      lastInvoke = now;
      pendingArgs = null;
      pendingThis = null;
      fn.apply(this, args);
      return;
    }
    if (trailing) {
      pendingArgs = args;
      pendingThis = this;
      schedule(wait - elapsed);
    }
  }

  throttled.cancel = () => {
    if (timer !== null) clearTimeout(timer);
    timer = null;
    pendingArgs = null;
    pendingThis = null;
    started = false;
    lastInvoke = 0;
  };

  throttled.flush = () => {
    if (timer !== null) {
      clearTimeout(timer);
      timer = null;
    }
    invoke();
  };

  return throttled;
}

const send = throttle((value) => console.log("sent", value), 200, {
  leading: true,
  trailing: true,
});
send(1);
send(2);
setTimeout(() => send(3), 250);