window.setInterval は無限ループしたり実行順を保証できない場合がある

window.setInterval の問題点

window.setInterval には window.setTimeout にない2つの問題点があります。

  • 致命的なエラーが発生してもタスクを繰り返し実行する。
  • 2秒かかるタスクを1秒おきに予約すると、現在のタスクが終わる前に次のタスクが始まる。

エラーが発生してもタスクを繰り返し実行する問題

/**
 * (サンプル1) window.setTimeout
 */

function timeoutTask () {
  var i = 0;
  var timeId;

  function handle () {
    throw new Error(i); // Error: 0

    if (i++ < 10) {
      clearTimeout(timeId);
      timeId = setTimeout(handle, 1000);
    } 
  }

  handle();
}

timeoutTask();


/**
 * (サンプル2) window.setInterval
 */
function intervalTask () {
  var i = 0;
  var timeId;

  function handle () {
    throw new Error(i); //  Error: 0

    if (++i > 9) {
      clearInterval(timeId);
    } 
  }

  timeId = setInterval(handle, 1000);
}

intervalTask();

window.setInterval ではエラーを無限に繰り返し続けます。++i を実行する前にエラーが発生しているところに問題があります。

(サンプル2) は1秒後からタスクを実行していますが、(サンプル1) と同じように即時実行型にすることでこの問題を回避できます。

  handle();
  timeId = setInterval(handle, 1000);

handle(); で Error になるので、setInterval が実行される前にスクリプトを終了することが出来ます。

現在のタスクが終わる前に次のタスクが始まる可能性がある問題

例えば、2秒かかるタスクを1秒おきに予約すると、現在のタスクが終わる前に次のタスクが始まります。場合によっては次のタスクが終わる時刻も現在のタスクより早いかもしれません。

var i = 0;
var timeId;
var busy; // ビジー状態を表す変数

function handle () {
  if (busy) { // ビジー状態なら1秒後に試行する
    clearInterval(timeId);
    timeId = setInterval(handle, 1000);
    return;
  }

  busy = true; // ビジー状態のフラグを立てる
  // 2秒以上かかる処理

  if (++i > 9) {
    clearInterval(timeId);
  } 
}

timeId = setInterval(handle, 1000);

このようにジー状態であることを認識するようにコードを書けば回避できますが、これほど複雑なコードを書くぐらいなら window.setTimeout を採用する方が現実的だと思います。

結論

window.setInterval は実行順を保証できず、前後の処理が衝突する場合があり、無限にエラーが増殖する場合があります。
window.setTimeout と比較すると欠点ばかりでいいところが見つからなかったのが残念です…。