🏈

TypeScript で引数まで含めた関数呼び出しごとに debounce したい!

2023/12/22に公開

次のような関数を定義しておけばよい。

debounceBasedOnArgs.ts
import type { DebounceSettings, DebouncedFunc } from 'lodash';
import debounce from 'lodash.debounce';
import memoize from 'lodash.memoize';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function debounceBasedOnArgs<R, F extends (...args: any[]) => R>(
  fn: F,
  wait = 0,
  options: DebounceSettings = {}
): (...args: Parameters<F>) => ReturnType<F> | undefined {
  const debouncedAndMemoized = memoize<(...args: Parameters<F>) => DebouncedFunc<F>>(
    function debounced() {
      return debounce(fn, wait, options);
    },
    (...args: Parameters<F>) => JSON.stringify(args)
  );

  return function wrappedFunction(...args: Parameters<F>): ReturnType<F> | undefined {
    return debouncedAndMemoized(...args)(...args);
  };
}

使い方は次のように。

const logEvent = debounceBasedOnArgs(
  (eventName: string, parameters: Record<string, string>) => {
    gtag("event", eventName, parameters);
  },
  100
);

logEvent("tap_item", {
  name: "Anpanman",
});
logEvent("tap_item", {
  name: "Currypanman",
});
logEvent("tap_item", {
  name: "Shokupanman",
});
logEvent("tap_item", {
  name: "Anpanman",
});

こうすることで Anpanmantap_item イベントは一度しか記録されないようになる。

解説

元ネタはlodash の GitHub issue にあったこのコメント

memoize 関数の第 2 引数である resolver に、引数を元にユニークな文字列を作るための関数を指定している。 memoize は内部で引数と関数の結果をペアにしてキャッシュしているが、デフォルトではその際に使われる引数は第 1 引数のみとなっている。今回やりたいこととは違うので resolver を指定している。ほかは理解しやすいよう、型定義を多少いじっている。

GitHubで編集を提案

Discussion