【JavaScript】setTimeout の使い方|遅延実行・clearTimeout・delay 0 の仕組み・Promise 化・デバウンスまで解説

setTimeout指定した時間が経過した後に関数を 1 回だけ実行するタイマー関数です。通知の自動消去、遅延リダイレクト、API リトライ、デバウンスなど、「一定時間待ってから実行する」操作の基本となるメソッドです。

この記事でわかること
・setTimeout の基本構文と戻り値
・clearTimeout でタイマーをキャンセルする方法
・delay 0 の仕組みとイベントループの関係
・setTimeout を Promise 化して async/await で使う方法(sleep 関数)
・デバウンス(入力の待ち合わせ)パターン
・通知の自動消去・遅延リダイレクト・API リトライの実務パターン
・setInterval との使い分け
スポンサーリンク

setTimeout の基本構文

構文
const timerId = setTimeout(callback, delay, arg1, arg2, ...);
// callback: 遅延後に実行する関数
// delay:    遅延時間(ミリ秒)。1000 = 1秒。省略すると 0
// arg1...:  callback に渡す引数(省略可)
// 戻り値:   タイマーID(clearTimeout で取り消しに使う)
基本例
// 2秒後にメッセージを表示
setTimeout(() => {
  console.log("2秒が経過しました");
}, 2000);
引数を渡す
function greet(name, greeting) {
  console.log(`${greeting}, ${name}!`);
}

// 1.5秒後に greet("田中", "こんにちは") を実行
setTimeout(greet, 1500, "田中", "こんにちは");
// → "こんにちは, 田中!"
第 1 引数に文字列を渡さないでくださいsetTimeout("alert(1)", 1000)eval と同じように文字列を評価するため、XSS の原因になります。必ず関数(アロー関数・関数参照)を渡しましょう。

clearTimeout でタイマーをキャンセルする

setTimeout の戻り値(タイマー ID)を clearTimeout に渡すと、まだ実行されていないタイマーをキャンセルできます。

JavaScript
const timerId = setTimeout(() => {
  console.log("この処理は実行されません");
}, 3000);

// 実行前にキャンセル
clearTimeout(timerId);
ボタンクリックでキャンセルする例
const timerId = setTimeout(() => {
  window.location.href = "/next-page";
}, 5000);

document.getElementById("cancelBtn").addEventListener("click", () => {
  clearTimeout(timerId);
  console.log("リダイレクトをキャンセルしました");
});
既に実行済みのタイマーに clearTimeout を呼んでもエラーにはなりません。安全に呼び出せるため、念のため clearTimeout するパターンは問題ありません。

delay 0 の仕組み(イベントループ)

setTimeout(fn, 0) は「0ms 後に実行」ではなく、現在の同期処理がすべて完了した後、次のイベントループで実行するという意味です。

JavaScript
console.log("1: 同期処理 開始");

setTimeout(() => {
  console.log("3: setTimeout(0) の中");
}, 0);

console.log("2: 同期処理 終了");

// 出力順序:
// "1: 同期処理 開始"
// "2: 同期処理 終了"
// "3: setTimeout(0) の中"

この仕組みを図解すると次のようになります。

フェーズ 実行される処理
同期処理 console.log("1")setTimeout を登録 → console.log("2")
コールスタックが空になる
タスクキュー setTimeout のコールバックが実行される → console.log("3")

delay 0 の実用的な使い方

DOM 更新後に処理を実行する
// innerHTML の変更は同期だが、ブラウザの描画は非同期
element.innerHTML = "<p>新しいコンテンツ</p>";

// setTimeout(0) でブラウザが描画する余地を作る
setTimeout(() => {
  // ここでは新しいコンテンツが画面に表示されている
  element.querySelector("p").classList.add("fade-in");
}, 0);
ブラウザの最低遅延は約 4ms です。setTimeout(fn, 0) でも実際には 4ms 程度の遅延があります。ネストが深くなると(5 段階以上)最低 4ms が保証されます。

setTimeout を Promise 化する(sleep 関数)

setTimeout は async/await と直接組み合わせられません。Promise でラップした sleep 関数を作ると、非同期処理の中で「N ミリ秒待つ」を直感的に書けます。

sleep 関数
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 使用例
async function main() {
  console.log("開始");
  await sleep(2000);  // 2秒待つ
  console.log("2秒後");
  await sleep(1000);  // さらに1秒待つ
  console.log("合計3秒後");
}

main();
キャンセル可能な sleep
function cancellableSleep(ms) {
  let timerId;
  const promise = new Promise((resolve, reject) => {
    timerId = setTimeout(resolve, ms);
  });
  promise.cancel = () => clearTimeout(timerId);
  return promise;
}

// 使用例
const wait = cancellableSleep(5000);
// 途中でキャンセル
wait.cancel();

デバウンス(入力の待ち合わせ)

デバウンスは「最後のイベントから一定時間経過するまで処理を遅らせる」テクニックで、setTimeout と clearTimeout の組み合わせで実装します。検索フォームのリアルタイム検索やウィンドウリサイズの処理に使います。

JavaScript
function debounce(fn, delay) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 使用例: 入力が止まって300ms後に検索を実行
const searchInput = document.getElementById("search");
const debouncedSearch = debounce((query) => {
  console.log("検索:", query);
  // fetch(`/api/search?q=${query}`) ...
}, 300);

searchInput.addEventListener("input", (e) => {
  debouncedSearch(e.target.value);
});

キー入力のたびに clearTimeout で前のタイマーをキャンセルし、新しいタイマーをセットすることで、入力が止まってから 300ms 後に 1 回だけ検索を実行します。

デバウンスの対義語がスロットル(一定間隔に 1 回だけ実行)です。スクロールイベントにはスロットル、入力イベントにはデバウンスが適しています。

setInterval との使い分け

比較項目 setTimeout setInterval
実行回数 1 回だけ 繰り返し実行
停止 clearTimeout(id) clearInterval(id)
繰り返し実行 再帰呼び出しで実現 自動で繰り返し
非同期処理との相性 良い(完了を待てる) 悪い(実行が積み重なる可能性)
setTimeout の再帰で繰り返し実行
function repeat() {
  console.log("実行:", new Date().toLocaleTimeString());
  setTimeout(repeat, 1000); // 完了後に次をスケジュール
}

repeat();
詳しくは「setInterval の使い方」で setInterval と setTimeout 再帰の違いを比較しています。

実務でよく使うパターン

通知メッセージの自動消去

JavaScript
function showNotification(message, duration = 3000) {
  const el = document.createElement("div");
  el.className = "notification";
  el.textContent = message;
  document.body.appendChild(el);

  setTimeout(() => {
    el.style.opacity = "0";
    el.addEventListener("transitionend", () => el.remove());
  }, duration);
}

showNotification("保存しました", 2000);

遅延リダイレクト(キャンセル可能)

JavaScript
const msg = document.getElementById("redirect-msg");
let seconds = 5;

msg.textContent = `${seconds}秒後にリダイレクトします...`;

const countdownId = setInterval(() => {
  seconds--;
  msg.textContent = `${seconds}秒後にリダイレクトします...`;
  if (seconds <= 0) clearInterval(countdownId);
}, 1000);

const redirectId = setTimeout(() => {
  window.location.href = "/dashboard";
}, 5000);

// キャンセルボタン
document.getElementById("cancelBtn").addEventListener("click", () => {
  clearTimeout(redirectId);
  clearInterval(countdownId);
  msg.textContent = "リダイレクトをキャンセルしました";
});

API リトライ(指数バックオフ)

JavaScript
async function fetchWithRetry(url, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const res = await fetch(url);
      if (res.ok) return await res.json();
      throw new Error(`HTTP ${res.status}`);
    } catch (error) {
      console.error(`試行 ${attempt + 1} 失敗:`, error.message);

      if (attempt < maxRetries - 1) {
        // 指数バックオフ: 1秒 → 2秒 → 4秒
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`${delay}ms 後にリトライ...`);
        await new Promise(r => setTimeout(r, delay));
      }
    }
  }
  throw new Error(`${maxRetries}回リトライしましたが失敗しました`);
}

this の問題とアロー関数

JavaScript
class Timer {
  constructor() {
    this.count = 0;
  }

  start() {
    // NG: 通常関数では this が変わる
    // setTimeout(function() { this.count++; }, 1000);

    // OK: アロー関数で this を保持
    setTimeout(() => {
      this.count++;
      console.log(this.count); // 1
    }, 1000);
  }
}

new Timer().start();

関連記事

よくある質問

QsetTimeout(fn, 0) は本当に 0ms 後に実行されますか?
Aいいえ。ブラウザには最低約 4ms の遅延があります。また、現在の同期処理と既にキューに入っている処理がすべて完了してから実行されるため、実際の遅延は環境によって異なります。0ms は「次のイベントループサイクルで実行する」という意味です。
QsetTimeout のコールバック内で this が undefined になります。
A通常の function を setTimeout に渡すと、this はグローバルオブジェクト(strict mode では undefined)になります。アロー関数を使えば外側の this を保持できます。setTimeout(() => this.method(), 1000)
QsetTimeout を async/await で使いたいのですが。
Afunction sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } と Promise でラップした sleep 関数を作り、await sleep(2000) のように使います。
QclearTimeout を呼んでもコールバックが実行されてしまいます。
AclearTimeout はまだ実行されていないタイマーのみキャンセルできます。コールバックが既に実行中の場合はキャンセルできません。タイマーをセットした直後に clearTimeout を呼んでいるか、delay 時間が十分かを確認してください。
Qデバウンスとスロットルの違いは何ですか?
Aデバウンスは「最後のイベントから N ms 経過後に 1 回だけ実行」、スロットルは「N ms に 1 回だけ実行(間引き)」です。検索入力にはデバウンス、スクロールイベントにはスロットルが適しています。

まとめ

setTimeout は指定時間後に関数を 1 回だけ実行するタイマー関数です。

  • 基本: setTimeout(fn, delay) で delay ミリ秒後に fn を 1 回実行
  • キャンセル: clearTimeout(id) で実行前のタイマーを取り消し
  • delay 0: 次のイベントループサイクルで実行(DOM 更新後の処理に有用)
  • Promise 化: sleep 関数で async/await と組み合わせ可能
  • デバウンス: setTimeout + clearTimeout で入力の待ち合わせを実装

通知の自動消去、遅延リダイレクト、API リトライなど、「一定時間待ってから実行する」操作の基本として幅広く活用できます。繰り返し実行が必要な場合は setInterval との使い分けも押さえておきましょう。