はじめに
ボタンを押すとサーバーに何かを送信する場合を考えてみましょう。ユーザーの行動は予測できないため、悪意や遊び半分でボタンを連打するユーザーも存在します。
この場合、押した回数だけサーバーへリクエストが送られるため、サーバーに不要な負荷がかかってしまいます。
こうした問題を防ぐために、フロントエンドではdebounce(デバウンス)というテクニックがよく使われます。
debounce(デバウンス)とは?
debounce(デバウンス)とは、連続したイベントの発火を制限し、最後の操作から一定時間が経過した後にのみ処理を実行するテクニックです。
特に以下のようなケースでよく利用されます。
- 検索フォームの入力
- ボタンの連打防止
- ウィンドウリサイズ
- スクロールイベント
例えば、以下のような検索フォームがあります。フォームに何かが入力するたびに、API通信を行います。

今のままだとサーバーに負荷がかかってしまうので、debounce処理を入れて、一定時間内は処理を待機し、最後の1回だけ実行するようにします。
debounce(デバウンス)の仕組み
const debounce = (callback, delay) => {
let timer;
return (...args) => {
if (timer) clearTimeout(timer); // 前のタイマーをリセット
timer = setTimeout(() => {
callback(...args); // delay後に実行
}, delay);
};
};
// 使用例
const debouncedSend = debounce(() => {
console.log("サーバーに送信成功"); // 最後の操作から3000ms後に1回だけ実行
}, 3000);
button.addEventListener('click', debouncedSend);
ユーザーがボタンを連打するたびに、タイマーがリセットされます。最後にボタンが押されてから3000ms間、操作がなかった場合にはじめてサーバーへ1回だけ送信されます。
追記しておくと、debounceはクロージャを利用した関数であるため、関数ごとに独立したtimerをクロージャ内に保持しています。そのため、複数のdebounce関数が互いに干渉することなく、それぞれ独立して動作します。
Typescript版と例のコード
const debounce = <T extends (...args: any[]) => void>(
callback: T,
delay: number
) => {
let timer: ReturnType<typeof setTimeout>;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
callback(...args);
}, delay);
};
};
ジェネリクスTで関数の型を受け取り、Parameters<T>でその関数の引数型を再利用するようにしました。

