JavaScript | Web API:タイマー・スケジューリング - 無限ループ防止

JavaScript JavaScript
スポンサーリンク

「無限ループ防止」は「止まる出口を必ず用意する」という発想

タイマーやループを書いているときに一番やっちゃいけないのが、
「永遠に終わらない処理」=無限ループ です。

ブラウザが固まる
CPU が 100% になってファンが回り続ける
ページがまったく操作できなくなる

こうなると、ユーザーから見れば「バグってるサイト」です。

無限ループ防止の本質は、
「必ずどこかで止まる条件(出口)を用意しておく」 ことです。
特に、タイマー系(setIntervalrequestAnimationFrame のループ)では、
「止める設計」を最初からセットで考えるのが超重要です。


まずは「典型的な無限ループ」のイメージを掴む

while や for の「出口がない」パターン

一番分かりやすい無限ループはこれです。

while (true) {
  console.log("止まらない…");
}
JavaScript

条件がずっと true なので、
永遠に終わりません。

もう少し現実的な「やらかし例」はこう。

let i = 0;

while (i < 10) {
  console.log(i);
  // i++ を書き忘れた!
}
JavaScript

i が増えないので、
i < 10 がずっと true のままです。

ここから学べるのは、

ループには
「条件」と「状態の変化」がセットで必要
状態が変わらないと、条件も変わらない

ということです。

タイマーでも「出口なしループ」は簡単に作れてしまう

タイマーを使ったループも、
設計をミスると「実質無限ループ」になります。

setInterval(() => {
  console.log("ずっと動き続ける");
}, 1000);
JavaScript

これ自体は悪くないですが、
「いつ止めるか」を考えていない という意味では、
「無限ループ予備軍」です。

requestAnimationFrame も同じです。

function loop() {
  // 何かする
  requestAnimationFrame(loop); // 永遠に呼び続ける
}

requestAnimationFrame(loop);
JavaScript

これも「止める条件」がなければ、
ずっと動き続けます。


タイマー系で無限ループを防ぐ基本パターン

setInterval は「必ず clearInterval とペアで考える」

setInterval を使うときは、
「どこで clearInterval するか」 を必ずセットで考えます。

例えば、10 回だけ実行したいならこう。

let count = 0;

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

  if (count >= 10) {
    clearInterval(id); // ここが出口
  }
}, 1000);
JavaScript

ここで重要なのは、

「いつ止めるか」をコードの中に明示している
条件が満たされたら必ず clearInterval が呼ばれる

という「出口の設計」です。

requestAnimationFrame も「終了条件」を必ず書く

アニメーションのループも同じです。

let x = 0;

function animate() {
  x += 2;
  box.style.left = `${x}px`;

  if (x < 300) {
    requestAnimationFrame(animate); // まだ動かす
  } else {
    // ここで終了(次の requestAnimationFrame を呼ばない)
  }
}

requestAnimationFrame(animate);
JavaScript

requestAnimationFrame のループは、

「次のフレームを予約するかどうか」を
自分で決められる

というのがポイントです。

「もう動かさなくていい」と判断したら、
次の requestAnimationFrame を呼ばない。

これが出口になります。


「回数で止める」「時間で止める」「状態で止める」

回数で止める(カウンタを使う)

一番シンプルなのは「何回まで」と決める方法です。

let count = 0;
const max = 5;

const id = setInterval(() => {
  count++;
  console.log(`実行 ${count} 回目`);

  if (count >= max) {
    clearInterval(id);
  }
}, 1000);
JavaScript

このパターンは、

リトライ処理(最大 3 回まで試す)
一定回数だけアニメーションする

などにそのまま使えます。

時間で止める(開始時刻からの経過で判断)

「◯秒経ったら止める」というパターンもよく使います。

const start = Date.now();
const limit = 5000; // 5秒

const id = setInterval(() => {
  const elapsed = Date.now() - start;

  console.log(`経過: ${elapsed} ms`);

  if (elapsed >= limit) {
    clearInterval(id);
    console.log("5秒経ったので終了");
  }
}, 200);
JavaScript

ここでは、

タイマーの回数ではなく
「本当の経過時間」で止める

という設計になっています。

状態で止める(フラグや条件で制御)

「ある条件が満たされたら止める」というのもよくあります。

let running = true;

function loop() {
  if (!running) return; // ここが出口

  console.log("ループ中…");
  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);

// どこかのタイミングで
running = false;
JavaScript

このように、
「フラグを見てループを抜ける」 という形にしておくと、
外からも止めやすくなります。


「無限ループを作らないための思考のクセ」

ループを書く前に「いつ終わるか」を言語化する

コードを書く前に、
自分にこう問いかけてみてください。

「この処理は、どんな条件で終わるべき?」

例えば、

10 回実行したら終わる
300px まで動いたら終わる
5 秒経ったら終わる
成功したら終わる(成功するまでリトライ)

など、「終わりの条件」を日本語で言えるかどうか が大事です。

言葉にできたら、それをそのままコードに落とします。

「10 回実行したら終わる」
count >= 10 なら clearInterval

「300px まで動いたら終わる」
x >= 300 なら次の requestAnimationFrame を呼ばない

この「出口を先に決める」クセが、
無限ループ防止の一番の武器になります。

「状態が変わらないループ」は危険信号

ループの中で、
ループ条件に関係する変数が変化していない ときは要注意です。

let i = 0;

while (i < 10) {
  console.log(i);
  // i を変えていない → 危険
}
JavaScript

タイマーでも同じで、

フラグを見ているのに、そのフラグをどこでも変えていない
カウンタを見ているのに、カウンタを増やしていない

こういうコードは、
「出口がないかもしれない」と疑ってください。


実際に「危ないコード」を安全に書き換えてみる

危ない例:setInterval を止めることを考えていない

setInterval(() => {
  console.log("サーバーに状態を取りに行く");
  // fetch("/api/status") ...
}, 1000);
JavaScript

これだと、
ページを離れても、
コンポーネントが破棄されても、
永遠にリクエストを送り続けます。

安全な形に書き換えるとこうなります。

let timerId = null;

function startPolling() {
  if (timerId !== null) return;

  timerId = setInterval(() => {
    console.log("サーバーに状態を取りに行く");
  }, 1000);
}

function stopPolling() {
  if (timerId !== null) {
    clearInterval(timerId);
    timerId = null;
  }
}
JavaScript

これで、

必要なときだけ startPolling()
不要になったら stopPolling()

という「開始」と「終了」がセットになります。

危ない例:requestAnimationFrame を永遠に呼び続ける

function loop() {
  // 何か重い処理
  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);
JavaScript

これも、
止める手段がないループ です。

終了条件を入れるとこうなります。

let running = true;
let frameCount = 0;

function loop() {
  if (!running) return;

  frameCount++;
  console.log(`frame: ${frameCount}`);

  if (frameCount >= 300) {
    running = false; // 300フレームで終了
    return;
  }

  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);
JavaScript

ここでは、

フラグ(running)
カウンタ(frameCount)

を使って、
「どこで終わるか」を明示しています。


初心者として「無限ループ防止」で本当に掴んでほしいこと

無限ループ防止の本質は「必ず出口を用意する」こと
setInterval は必ず clearInterval とペアで設計する(どこで止めるかを先に決める)
requestAnimationFrame のループも「次を予約しない」という形で終了条件を書く
回数・時間・状態(フラグ)など、何をもとに「終わり」を判断するかをコードに落とす
ループを書く前に「この処理はどんな条件で終わるべき?」と自分に問いかけるクセをつける

小さな練習として、

1 秒ごとに数字を増やすタイマー
→ 10 まで行ったら止める
→ ボタンを押したら途中でも止められる

みたいなコードを書いてみてください。

「動き続けるものには、必ず止まる条件をつける」
この感覚が一度身体に入ると、
タイマーでもループでも、“暴走しないコード” を自然に書けるようになります。

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