【JavaScript】setTimeoutとは?基本的な使い方から実践的なコード例まで初心者にもわかりやすく解説

はじめに

JavaScriptを学び始めると、画面上で一定時間後に何かの処理を実行する方法や、短い待ち時間を挟んでから後続の動作を行う仕組みを知りたいと思う人が多いのではないでしょうか。
そこで登場するのが、setTimeout です。
Webページを開いた後、数秒後にポップアップを表示したり、フォーム送信が終わったあとに少し間を置いて画面をリロードしたりするときなど、さまざまな使い道があります。

ただし、非同期処理を扱うためには、多少の慣れや注意も必要です。
うまく使わないと意図しないタイミングで処理が実行されたり、コードが複雑になり過ぎたりします。
そこで今回は、setTimeoutの基本から実務での活用シーン、コールバック関数との組み合わせ、複数のタイマーを使う際の注意点、そしてPromiseを交えたより洗練された書き方までを紹介します。

この記事を読むとわかること

  • setTimeoutの基本的な仕組み
  • 実務での活用シーンやメリット・注意点
  • コールバック関数との組み合わせや複数タイマー管理のコツ
  • PromiseやAsync/Awaitなどを用いた応用的な書き方

これらを理解することで、JavaScriptにおける非同期処理の導入的なハードルを下げられるでしょう。

setTimeoutとは

ここでは、setTimeoutがどのような仕組みで動いているか、そして何を解決してくれるのかを確認しましょう。
プログラミング初心者の方でも、ざっくりとイメージをつかめる内容を目指します。

setTimeoutの基本的な仕組み

setTimeoutは、指定したミリ秒後に特定の処理を1回だけ実行するためのJavaScript関数です。
タイマーのように見えますが、あくまで「一定時間が経過した後に、その処理をキューに追加する」動きをします。
そのため、時間が来たら即座に関数が実行されるというよりは、「イベントキュー」に積まれ、メインスレッドの処理が空いたタイミングで実行されるイメージです。

シングルスレッドで動くJavaScriptでは、いわゆるメインスレッドが他の処理(ループや重い計算など)でブロックされていると、タイマーの時間が過ぎても実行が遅れる可能性があります。
これを知っておくだけでも、後述するパフォーマンスのところで混乱しにくくなるでしょう。

setTimeoutの構文と引数

setTimeoutの基本構文は以下のとおりです。

setTimeout(関数または文字列, 遅延時間[, 引数1, 引数2, ...]);

第一引数には実行する関数を渡します。
文字列を渡すこともできますが、推奨されません。
第二引数には遅延時間をミリ秒単位で指定します。
例として、2000ミリ秒(=2秒)後に処理を実行したい場合は setTimeout(callback, 2000) のように書きます。

なお、第三引数以降はcallback関数のパラメータに渡すための引数を指定できます。
たとえば以下のように書くと、2秒後に showMessage("Hello") を呼び出せます。

function showMessage(text) {
  console.log(text);
}

setTimeout(showMessage, 2000, "Hello");

こうした書き方をすることで、後から関数の引数を増やしたり管理したりする際に便利です。

setTimeoutの実務での活用シーン

実際の業務では、setTimeoutをどのように使うのでしょうか。
単に「少し待ってから○○する」というだけではなく、UI/UXの向上やバックエンドとの通信にも役立ちます。

ページ読み込み後に特定の処理を行いたい場合

たとえば、ページが読み込まれた直後にメッセージや広告バナーを表示する場面があります。
ただし、即時に表示するとユーザー体験が損なわれる可能性もありますよね。
そこでsetTimeoutを使い、ページ読み込み直後から数秒経ってからバナーを表示すれば、ユーザーが少し落ち着いた段階で必要な情報を自然に目にすることができます。

また、データの取得タイミングを少し遅らせたい場合にも利用できます。
初期表示を素早く行い、後から余力をもって追加コンテンツをロードする、といったケースです。
こうすることで、画面が完全に表示されるまでの時間を短縮できたりする効果も狙えるでしょう。

モーダルウィンドウの自動クローズなどのUI操作

モーダルウィンドウが表示されてから一定時間が経ったら自動で閉じたい、というシチュエーションでは、setTimeoutはとても役立ちます。
たとえば、ユーザーに「操作が完了しました」とメッセージを表示してあげてから3秒後に閉じる、という設計が考えられます。
以下のように書くと、3秒後にモーダルを閉じることが可能です。

const modalElement = document.getElementById("myModal");

// モーダルを開く処理は省略
setTimeout(() => {
  modalElement.style.display = "none";
}, 3000);

ユーザーの操作なしでUIが変化するので、あらかじめ「自動で閉じる」ということを案内しておくと親切です。
このように、setTimeoutはUXを調整する上でもよく利用されています。

コールバックを使った非同期処理

JavaScriptでは、非同期処理が重要な役割を果たします。
ここでは、setTimeoutにコールバック関数を渡す方法や、いわゆる「コールバック地獄」について触れます。

setTimeoutとコールバック関数

コールバック関数とは、ある関数に引数として渡される関数のことです。
setTimeoutの第一引数には、実行したい処理をまとめた関数を渡すため、コールバック関数を利用する代表的なケースの1つといえます。
たとえば、2秒後にサーバーからデータを取得する関数を呼び出したい場合などに重宝します。

コールバックを用いると、非同期処理を直感的に記述できる反面、処理内容が増えてくるとコードが入り組んで読みづらくなる可能性があります。
これは「コールバック地獄」とも呼ばれ、階層が深くなるにつれてメンテナンスが難しくなるのが特徴です。

コールバック地獄への理解

多くのコールバックが連鎖すると、以下のような構造になりがちです。

setTimeout(() => {
  console.log("Task 1 完了");
  setTimeout(() => {
    console.log("Task 2 完了");
    setTimeout(() => {
      console.log("Task 3 完了");
      // さらにネスト...
    }, 1000);
  }, 1000);
}, 1000);

処理内容自体は単純でも、入れ子になったコードは可読性が下がります。
こういった場合には、PromiseやAsync/Awaitを使うことで読みやすい構造に変えられます。
「最初はコールバックで慣れつつ、必要に応じてPromiseやAsync/Awaitに切り替える」という流れも自然ですので、段階的に学ぶとわかりやすいでしょう。

setTimeoutを使ったタイマーアプリの簡単な例

ここでは、学習のために簡単なカウントダウンタイマーの例を見ていきます。
「指定した秒数がゼロになるまでカウントダウンしていく」というシンプルな仕様です。

カウントダウンタイマーのサンプルコード

let remainingTime = 5;

function countDown() {
  console.log(`${remainingTime}秒前...`);
  remainingTime -= 1;
  
  if (remainingTime >= 0) {
    setTimeout(countDown, 1000);
  } else {
    console.log("タイマーが終了しました。");
  }
}

// タイマーをスタート
countDown();

サンプルコードの解説

最初に remainingTime を5秒に設定しています。
countDown 関数を呼ぶと、現在の秒数を表示した後に remainingTime を1ずつ減らしています。
そして、残り時間が0以上であれば再び setTimeout(countDown, 1000) として、1秒後に同じ関数を呼び出します。
こうすることで、繰り返しの処理を実現しているわけです。

もし5からスタートして3秒後にキャンセルが必要なら、後述するclearTimeoutを使い、指定の条件下でタイマーを停止できます。
小規模な機能であればこれで十分な場面もありますが、多くのタイマーを同時に扱う場合には、管理方法をしっかり考えたほうが良いでしょう。

setTimeoutの遅延実行のメリットと注意点

処理を遅延実行することによって得られるメリットは大きいものの、落とし穴もあります。
ここでは、メリットと注意点を整理します。

メリット

実行タイミングを制御できるため、主に以下のような利点があります。

ユーザーの操作をサポート

モーダルやポップアップが即座に出てこないようにする、一定時間後にフォームを自動送信するなどの調整がしやすくなります。

UI/UXの向上

長い処理をすぐに見せるのではなく、ユーザーの第一印象を保ちつつ必要なタイミングだけ処理を行うなどの工夫が可能です。

依存関係のある処理を一時的に後回し

初期表示が重たくなる場合、一部の処理をsetTimeoutで遅らせてパフォーマンスを調整できます。

注意点

一方で、注意しなければならないポイントもあります。

指定時間通りに必ず実行されるわけではない

JavaScriptはシングルスレッドで動作するため、メインスレッドが他の処理で詰まっていると、setTimeoutのコールバックは後回しになる可能性があります。

順序が分かりづらくなる

あるタイミングで複数の処理を遅延実行した場合、実行順序が錯綜しないように設計しないと、意図せぬ動作の原因になりがちです。

コードの可読性が下がるリスク

先ほどのコールバック地獄のように、追加・変更が続くとメンテナンスが難しくなります。

これらを踏まえ、シンプルな機能であれば問題ないのですが、大規模なアプリで多用する場合には、他の手法も検討したほうがスムーズです。

複数のsetTimeoutを扱うときのコツ

複数のタイマーを扱う場合、同時に何個かの処理が遅延実行される可能性があります。
ここでは依存関係のあるタイマーの制御や、キャンセルの方法について説明します。

依存関係のあるタイマーの制御

たとえば、「ユーザーがボタンをクリックしてから1秒後に画面を更新し、その処理が終わってから2秒後にアラートを出す」といったシナリオが考えられます。
この場合、先に行う処理が終わることを待ってから次のタイマーをセットするほうが、意図通りの流れを実現しやすいでしょう。

ただ、処理の結果によっては「2秒後のアラートは出したくない」というケースもあり得ます。
こういった条件分岐を行うときこそ、コールバックよりPromiseやAsync/Awaitを検討するのが有力です。
状況に応じて、依存関係の設計をシンプルにまとめる工夫が必要になります。

キャンセル(clearTimeout)の使い方

一度 setTimeout を呼び出した後に「やっぱりキャンセルしたい」となったら、clearTimeout でタイマーを取り消せます。
以下のように、変数にタイマーIDを保持し、必要なときにそれをキャンセルする形です。

const timerId = setTimeout(() => {
  console.log("もう遅延時間が経過しました。");
}, 5000);

// キャンセルしたい場合
clearTimeout(timerId);

setTimeout 関数の戻り値を使ってキャンセルするので、複数のタイマーを扱うときは、管理しやすい形でIDを保存しておくと便利です。
これを怠ると、必要のないタイマーが動き続けてしまい、想定外のタイミングで処理が発火する原因になります。

setTimeoutとPromiseの組み合わせ

コードをすっきり書きたい場合や、複雑な非同期シナリオに対応する場合は、Promiseを使うことがよくあります。
ここでは、setTimeoutとPromiseを一緒に活用する方法と、Async/Awaitとの連携について解説します。

Promiseを使った待機処理

setTimeoutをPromiseにラップすることで、待機処理をより直感的に書けます。
例えば以下のように書くと、任意のミリ秒だけ待機するPromiseを作成できます。

function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}

// 3秒待機してから実行
wait(3000).then(() => {
  console.log("3秒待ちました。");
});

これだけでも、ネストが深いコールバック構文から解放されます。
resolve() が呼ばれると、.then() の中の処理が実行される流れです。

Async/Awaitとの連携

Async/Awaitを使えば、Promiseベースのコードをさらに読みやすく表現できます。
先ほどの例をAsync/Awaitで書くと、以下のようになります。

async function runTask() {
  console.log("処理開始");
  await wait(3000);
  console.log("3秒後にこの行が実行されました。");
}

runTask();

このように、非同期処理を同期処理のように扱えるので、複数の待機を順番に行う場合でも可読性が上がります。
また、エラーハンドリングはtry-catch構文を使うことで見通しが良くなるでしょう。
setTimeoutと組み合わせて実行順序をコントロールする機会が増えたら、ぜひPromiseやAsync/Awaitも検討してください。

重複実行を避けるための設計例

タイマーを設定するとき、同じ関数が何度も呼ばれてしまって予期せぬ重複実行が起こることがあります。
ここでは、フラグを用いた制御方法や複数のsetTimeoutを管理する工夫を紹介します。

フラグを利用して多重実行を防止

もっとも簡単な方法は、実行中であることを示すフラグを立てる というものです。
以下の例では、ある処理がまだ動いているときには新たなsetTimeoutを設定しないようにしています。

let isRunning = false;

function runOnce() {
  if (isRunning) return;  // 既に実行中なら何もしない

  isRunning = true;
  
  setTimeout(() => {
    console.log("一度だけ動作する");
    isRunning = false;
  }, 1000);
}

ボタンを連打しても、実行中は isRunningtrue のため、新しいタイマーはセットされません。
一度の呼び出しだけで済むケースなら、こういったシンプルなフラグで十分です。

setTimeoutを正しく管理するテクニック

複数のタイマーを状況に応じてON/OFFしたい場合、以下のような工夫が考えられます。

タイマーIDをオブジェクトや配列で一元管理する

使用中のタイマーをまとめて確認でき、必要なときにまとめてクリアできる。

状態管理ツールやクラスを用いる

特定のUIコンポーネントごとに、どのタイマーが登録されているかを明確にする。
これにより、画面が切り替わる際にタイマーを一括で停止するなど柔軟に管理できる。

意外と見落とされがちですが、タイマー管理がしっかりしていないと予測不能なバグ が起きやすいため、開発時には注意が必要です。

似た機能を持つsetIntervalとの違い

JavaScriptには、setTimeoutのほかにも繰り返し実行を目的とした setInterval があります。
両者の特徴を把握すると、「どちらを使うべきか?」という判断がしやすくなります。

setIntervalの特徴

setIntervalは、指定したミリ秒間隔で繰り返し関数を呼び出す仕組みです。
例えば setInterval(fn, 1000) と書くと、1秒ごとに fn が実行され続けます。
ただし、メインスレッドがブロックされているときに次の実行タイミングが到来すると、実行が重なったり遅延して実行順序が狂う可能性もあります。

そのため、アニメーションやデータ更新のような「短い時間間隔で繰り返し行う処理」には向いている反面、タイミングがずれると困る場面では扱いが難しい場合があります。
特に、ある処理が完了していないのに次の処理が始まるとバグに繋がることも少なくありません。

setTimeoutとの比較と判断基準

setTimeoutとの大きな違いは、「1回限りの実行か、繰り返し実行か」 という点です。
ただ、繰り返し実行したいときでも、setIntervalを使うのではなく「setTimeoutを再帰的に呼び出す」やり方を選ぶことがあります。
なぜなら、処理が終わったタイミングで次のタイマーをセットする形 にすれば、タイミングのズレが少なく管理しやすいからです。

以下のように書くと、実質的にsetIntervalと同じことをsetTimeoutで実現できます。

function repeatTask() {
  // ここに繰り返したい処理を記述

  setTimeout(repeatTask, 1000); // 再度呼び出し
}

repeatTask();

こうすると、「ある処理が完了してから次の処理を呼び出す」という流れに合わせやすいメリットがあります。

パフォーマンスとブラウザ上での挙動

JavaScriptはメインスレッド上で動くため、重い処理や多数のsetTimeoutを多用すると、かえって画面操作がもたつくことがあります。
また、タブが非アクティブのときはタイマーの動作が抑制される場合もあるので、そこも知っておきましょう。

メインスレッドをブロックしない

setTimeout自体は比較的軽量な関数ですが、そのコールバックで重い処理を行えば、結局はメインスレッドを占有してしまいます。
たとえば、数万件のデータを一度に処理すると画面が固まってしまうかもしれません。
そのため、大きな作業を少しずつ分割して、一定間隔で実行するなどの工夫をすることが多いです。

このような分割処理は、setTimeoutやsetIntervalを使って「少し実行しては一旦メインスレッドに戻し、次のタイミングでまた処理を続ける」という形で実現できます。
ユーザーが操作中でも画面が止まりにくいので、UXが向上する場面もあるでしょう。

タブが非アクティブなときの挙動

最近のブラウザは省電力やパフォーマンスの観点から、タブがバックグラウンド状態になるとsetTimeoutやsetIntervalの呼び出し間隔を制限・遅延することがあります。
そのため、ユーザーがタブを切り替えて別のページを見ているときに期待通りのタイミングで処理が動かないケースがあるかもしれません。

もしバックグラウンドでも正確な時間を計測したい場合は、Web Workersやサービスワーカーなどの仕組みを使う必要が出てくることもあります。
ただ、通常のUI操作やちょっとしたアニメーション、バックグラウンドで動いていても問題が小さい作業などであれば、標準的なsetTimeoutで十分対応可能です。

トラブルシューティング

setTimeoutを使っていて意図した動きが得られないとき、どこを確認すれば良いのでしょうか。
ここでは、動作しない原因や予期しないタイミングで実行されるときの対処法を整理します。

setTimeoutが動作しない場合に考えられる原因

タイマーIDを正しく管理できていない

キャンセルしたつもりがないのに、実は clearTimeout を呼んでいたケースなど。
あるいは、再代入でIDを上書きしてしまい、結果として意図せずキャンセルされていることもあります。

コールバック関数の参照が間違っている

定義されていない関数名を渡したり、スコープ外の変数を参照しようとしてエラーが起きている場合。
エラーが発生していると、タイマーが設定されているにもかかわらず動作していないように見えることがあります。

HTML要素が存在しない・取得できていない

DOM操作が関わる処理で、要素の取得がうまくいっていないと何も起こっていないように見えます。
開発ツールのコンソールにエラーが出ていないかを確認すると良いでしょう。

予期しないタイミングで実行されるときの対処

メインスレッドのブロックを減らす

長いループなどが走っている場合、タイマーのコールバックは予定の時間より遅れて呼ばれます。
なるべく分割して処理する、またはWeb Workerを活用するなどの対策があります。

複数のsetTimeoutが競合していないかを確認

同じ変数に入ったタイマーIDを上書きしていないか、あるいはコンポーネントの再レンダリングの際に重複してタイマーがセットされていないかをチェックします。

想定外のclearTimeout呼び出し

ほかの箇所で clearTimeout を使っている影響で、一部のタイマーが停止してしまうケースがあります。
コールスタックや変数のスコープを確認し、無関係な場所でタイマーIDを使いまわしていないかを注意深く見ましょう。

タイマーに関連するバグは、複数のタイマーが同時に動作しているような複雑なコードで起こりやすい傾向があります。 デバッグ時には、どのタイミングでどのタイマーがセットされ、どこでキャンセルされているかを整理すると原因を特定しやすくなります。

setTimeoutを使う上でのベストプラクティス

最後に、実際の開発現場でも役立ついくつかのベストプラクティスを紹介します。
特に大規模なプロジェクトや、複数の開発者が関わるチーム開発では、可読性と管理のしやすさが最優先になります。

可読性を高めるための工夫

タイマー名を分かりやすくする

変数名や関数名にタイマーの用途を表す言葉を入れる。
例:setTimeout(showBanner, 3000)const bannerTimerId = setTimeout(showBanner, 3000);

配置場所に気をつける

setTimeoutを使うロジックが散らばると混乱しやすいので、なるべく関連する機能ごとにファイルやクラスを分ける。
UI関連のタイマーはUIのコンポーネント内にまとめる、といった分離が有効です。

コメントで意図を明記する

「なぜこの処理を遅延させるのか」「何をトリガーに次のステップが動くのか」をコメントしておくと、将来的なメンテナンスがしやすくなります。

タイマー管理の整理術

タイマーIDを連想配列やクラスのプロパティで管理

例:this.timers = { bannerTimer: null, modalCloseTimer: null }; のようにまとめ、必要に応じてクリアする。
こうすると「どこでタイマーが生まれ、どこで止まるのか」を一目で把握できるようになります。

ライフサイクルに合わせてクリア

SPAsなどのシングルページアプリケーションの場合、ページ遷移でも画面が破棄されないケースがあります。
使わなくなったタイマーをクリアしないまま放置しておくと、リソースの無駄遣いだけでなく予想外のタイミングで動き出す原因にもなるでしょう。
画面がアンマウント(破棄)される際や、コンポーネントが切り替わるタイミングでしっかり clearTimeout を呼び出す習慣をつけるのがおすすめです。

多くのsetTimeoutを組み合わせた大規模コードは、後から手を入れるほど混乱が生じやすいものです。 早めにPromiseやAsync/Awaitなどを活用したコード設計を検討してみると、後々のトラブルを減らせるでしょう。

まとめ

本記事では、JavaScriptのsetTimeout について基礎から実務での活用シーンまで幅広く見てきました。
単に「○秒後に実行する」というだけでなく、非同期処理 全体の理解を深めるうえでも重要な関数といえます。

setTimeoutを使うときに押さえておきたいポイントは、以下のとおりです。

非同期処理であることを意識する

シングルスレッドで動いているため、重い処理の最中はタイマーが遅れる可能性がある。

コード管理やキャンセル処理を丁寧に

どこでタイマーをセットし、どこでクリアするのかを明確にしておく。

PromiseやAsync/Awaitと組み合わせると読みやすくなる

コールバック地獄を避けるためにも、柔軟な書き方を習得しておくと役立つ。

実際のプロジェクトでは、モーダルの自動閉鎖やページ読み込み後のバナー表示など、ユーザーの行動やUI設計に合わせてタイマーの仕組みをうまく組み合わせる場面が多々あります。
今回学んだ内容を基に、ぜひさまざまなシチュエーションでの実装方法を考えてみてください。

JavaScriptをマスターしよう

この記事で学んだJavaScriptの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。