JavaScript | 非同期処理:コールバック – setInterval の仕組み

JavaScript JavaScript
スポンサーリンク

setInterval を一言でいうと何か

setInterval
「この処理を◯ミリ秒ごとにくり返し実行してね」とブラウザ(または Node)に頼む関数
です。

setTimeout が「1回だけ、あとで実行」なのに対して、
setInterval は「一定間隔で何度も実行」するための仕組みです。

ただし、仕組みの根っこはほぼ同じで、

  • 「時間を測る」のは JavaScript 本体ではなく Web API
  • 実行するときは、コールバックをタスクキューに入れてイベントループが拾う

という流れです。

ここが重要です。
setInterval は「一定間隔でタスクキューにコールバックを投げ続けるタイマー」 だとイメージしてください。
「間隔ぴったりで実行される魔法」ではありません。


setInterval の基本的な使い方と動き

もっともシンプルな例

let count = 0;

const id = setInterval(() => {
  count++;
  console.log("カウント:", count);
}, 1000);
JavaScript

このコードは、

  • 約 1 秒ごとに "カウント: 1", "カウント: 2", "カウント: 3" …と表示し続けます。
  • setInterval は「タイマー ID」を返してくるので、それを変数 id に保存しています。

止めたいときは clearInterval(id) を呼びます。

setTimeout(() => {
  clearInterval(id);
  console.log("停止しました");
}, 5000);
JavaScript

これで約 5 秒後にくり返しが止まります。

引数の意味

setInterval(コールバック関数, 間隔ミリ秒, 追加引数1, 追加引数2, ...)

  • 第1引数:毎回実行されるコールバック関数
  • 第2引数:2 回目以降の「実行の間隔」(ミリ秒)

追加引数はほぼ使われませんが、指定するとコールバックの引数として渡されます(昔の仕様)。
初心者のうちは、第1・第2引数だけ覚えれば十分です。


setInterval の裏側で何が起きているか

1回の実行の「サイクル」

シンプルに言うと、1 回ごとの流れはこうです。

  1. Web API に「◯ミリ秒ごとにこのコールバックを実行して」と依頼する(setInterval 呼び出し)
  2. Web API 側のタイマーが「時間経過」を監視する
  3. 指定時間が経過するたびに、「このコールバックをタスクキューに入れて」とイベントループ側に渡す
  4. イベントループが、コールスタックが空いたタイミングでそのコールバックを実行する

これが、間隔ごとに何度もくり返される だけです。

ここでのポイントは、

  • 時間を測っているのは Web API(JavaScript 本体じゃない)
  • 実行タイミングは「スタックが空いたとき」の都合に依存する

という 2 つです。

setTimeout との違い(1回 vs 何回も)

setTimeout は「1 回だけ」タスクキューにコールバックを投げて終わりですが、
setInterval は「指定した間隔ごとに何度も」タスクキューにコールバックを投げ続けます。

イメージとしては、

  • setTimeout:一回だけ鳴る目覚まし
  • setInterval:一定間隔で鳴り続けるアラーム

の違いに近いです。


「間隔ぴったり」ではない理由(ここを勘違いしがち)

実行されるのは「指定時間が経ったあと+JS側のタイミング次第」

例えば次のコードを考えてみます。

setInterval(() => {
  console.log("tick", Date.now());
}, 1000);
JavaScript

これは「おおよそ 1 秒ごと」に tick を出しますが、
ぴったり 1000ms ごと ではありません。

理由は、

  • Web API は 1000ms ごとに「コールバックをキューに入れていいよ」と準備する
  • しかし、コールスタックが忙しい(重い処理中など)と、イベントループはすぐにそれを実行できない
  • 結果として「1000ms 経過した時点」ではなく、「1000ms 経過していて、かつ JS が暇になったタイミング」でコールバックが走る

からです。

例えば、間に重い処理が挟まるとこうなります。

setInterval(() => {
  console.log("tick", Date.now());
}, 1000);

const start = Date.now();
while (Date.now() - start < 5000) {
  // 5秒間重い処理
}
JavaScript

この 5 秒間、イベントループは新たなタスクを実行できません。
その間に「何回分も tick がキューに溜まる」こともありますが、
実際に実行されるのは while が終わってからです。

ここが重要です。
setInterval の引数は「実行間隔を保証する値」ではなく、「このくらいの間隔で“実行候補をキューに入れる目安”」 だと理解してください。


setInterval の ID と clearInterval の仕組み

なぜ ID が返ってくるのか

const id = setInterval(...); のように書くと、id には「タイマーを識別する番号」が入ります。

裏側では、

  • setInterval が呼ばれるたびに、ブラウザは「新しいタイマーオブジェクト」を作る
  • それぞれに固有の ID がつく
  • clearInterval(id) とすると、その ID に対応するタイマーを止める

という動きになっています。

let count = 0;

const id = setInterval(() => {
  count++;
  console.log(count);

  if (count >= 3) {
    clearInterval(id);
    console.log("停止");
  }
}, 1000);
JavaScript

このコードでは、

  • 1秒ごとに 1, 2, 3 と出力
  • 3 回目が終わったところで clearInterval(id) によってタイマーが停止

という流れになります。


setInterval と setTimeout の実践的な違い

「setInterval でループ」 vs 「再帰的 setTimeout」

setInterval は一見便利ですが、
「処理時間を考慮しない」ために、場合によっては望ましくないことがあります。

setInterval(() => {
  // ここが思ったより時間のかかる処理
}, 1000);
JavaScript

この処理が 900ms かかるとすると、

  • 1000ms ごとに新しい実行がキューに乗る
  • しかし前の処理がまだ終わっていないかもしれない
  • 最悪、処理が重すぎて「前の処理が終わらないうちに次の tick が溜まり続ける」

といった状態になります。

一方、「再帰的 setTimeout」ならこうなります。

function tick() {
  const start = Date.now();

  // 時間のかかる処理(仮)
  while (Date.now() - start < 900) {}

  console.log("tick at", Date.now());

  setTimeout(tick, 1000);
}

setTimeout(tick, 1000);
JavaScript

これは、

  • 処理を 1 回実行する
  • 終わった「あと」で、1 秒待ってから次を実行する

という形になるので、

「処理時間+待ち時間」で間隔が決まる」=「処理が重くても詰まりにくい」
という特徴があります。

ここが重要です。
「必ず◯ms ごとに実行したい」のか、「1回の処理が終わってから◯ms 休んで次に行きたい」のかで、setInterval と再帰的 setTimeout で向いている場面が違う ということです。


初心者として押さえておきたい setInterval のポイント

どこまで理解しておけばいいか

今の段階で、次の感覚があれば十分です。

setInterval は「◯ミリ秒ごとにコールバックをタスクキューに投げるタイマー」である。

タイマーの管理(時間を測る)は Web API が行い、実際の実行はイベントループとタスクキューに任される。

指定した間隔は「ぴったりその時間で実行する」のではなく、「その時間ごとに“実行候補”をキューに入れる」イメージである。

返り値の ID を clearInterval に渡すことで、タイマーを止めることができる。

処理が重い場合や、前の処理の終了を待ってから次を実行したい場合は、「再帰的 setTimeout」のほうが設計しやすい場合がある。

簡単な練習のアイデア

理解を定着させるために、次のような小さなコードを書いてみると良いです。

秒数をカウントし続けるタイマー(1秒ごとに表示)を setInterval で作る
一定回数で自動停止するタイマーを作る(clearInterval を使う)
同じ処理を setInterval と 再帰的 setTimeout で書き、重い処理を入れて違いを感じてみる

このあたりを試すだけで、
「setInterval は『定期的にタスクキューに仕事を投げる仕組み』なんだな」
という実感がかなりはっきりしてきます。


まとめ

setInterval の本質を一文でまとめると、

「指定した間隔ごとに、コールバック関数をタスクキューに追加し続けるタイマー API」
です。

そこに、

時間を測るのは Web API
実際に実行するタイミングを決めるのはイベントループ
実行の順番・タイミングはタスクキューやコールスタックの状況に依存

という非同期の仕組みが関わっています。

ここまでの理解があると、

  • 「なぜ interval の間隔がズレるのか」
  • 「なぜ clearInterval が必要なのか」
  • 「なぜ場合によっては再帰的 setTimeout が好まれるのか」

といった疑問にも、自分で説明がつくようになります。

setTimeout の仕組みと合わせて頭に入れておくと、
JavaScript の「時間を扱う非同期処理」がかなりクリアに見えてきます。

タイトルとURLをコピーしました