PeriodicalExecuterクラス

【抜粋】
var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
}

典型的なprototype.jsのクラス作成方法により記述されています。Class.createメソッドでコンストラクタ関数を作成し、prototypeプロパティにinitializeメソッドと他のメソッドを追加しています。

使用方法は、関数と実行間隔秒数を指定してインスタンスを作成するだけです。以下のメソッドは外部から呼び出されることを想定していません。

initializeメソッド

指定された関数と実行間隔行数を保持し、実行状態フラグをfalse(実行していない)にしてregisterCallbackメソッドを呼び出します。

registerCallbackメソッド

setInterval関数により、onTimerEventメソッドを指定された実行間隔秒数ごとに、繰り返し実行させます。前述のFunction.bindメソッドにより、onTimerEventメソッドでthisが使用できます。

onTimerEventメソッド

実行状態フラグがfalse(実行していない)の場合に、フラグをtrue(実行中)にしてから、指定された関数を呼び出します。終了後に実行状態フラグをfalseに戻します。try〜finally節が使用されているのは、指定関数がエラー終了した場合でもフラグを戻すためです。

指定関数について

通常、指定関数には引数を指定できません。ですが、これも前述Function.bindメソッドを使用すれば指定することができます。

停止について。

※以下の内容はv1.4.0についてです。v1.5.0以降ではstopメソッドがあり、停止することが可能です。

PeriodicalExecuterで繰り返し実行すると、ページが変わるまで止められません。通常、setInterval関数が返却するタイマー識別子を使用して、clearInterval関数で停止できるのですが。タイマー識別子は捨てられてるみたいですし。*1なぜなのかはよく分からないです。。。

【例】
<html>
<head>
<title></title>
<script language="javascript" src="prototype.js" charset="utf-8"></script>
<script language="javascript">
<!--
var Test = Class.create();
Test.prototype = {
  initialize: function(a) {
    this.a = a;
  },  
  calc: function(b){
    this.a *= b;
    document.getElementById("elem").innerHTML = this.a;
  }
}
	
var test = new Test(1);

function init(){
  new PeriodicalExecuter(test.calc.bind(test, 2), 3);
}
	
function stop(){
  //止まりません^^;
  clearInterval();
  clearTimeout();
}
//-->
</script>
</head>
<body onload="init();">
<div id="elem">&nbsp;</div>
<button onclick="stop();">STOP(しない><;)</button>
</body>
</html>

Testクラスのインスタンスtestのcalcメソッドを、Fucntion.bindメソッドを使用してPeriodicalExecuterに渡しています。PeriodicalExecuterのインスタンスは用途がないので取得していません。ページ読み込み後、3秒毎に数を倍にしてdiv要素に書き込みます。STOPボタンは意味なしです^^;

以下、改修案です。registerCallbackでタイマー識別子を取得、stopメソッドとrestartメソッドを追加しました。

【改修案】
PeriodicalExecuter.prototype = {
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.tid = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },
  
  restart: function() {
    if(this.tid) return;
    this.registerCallback();
  },
  
  stop: function() {
    if(!this.tid) return;
    clearInterval(this.tid);
    this.tid = null;
  }

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
}
【上記改修案を用いた場合の例】
<html>
<head>
<title></title>
<script language="javascript" src="prototype.js" charset="utf-8"></script>
<script language="javascript">
<!--
//オーバーライド
PeriodicalExecuter.prototype = Object.extend(PeriodicalExecuter.prototype, {
  registerCallback: function() {
    this.tid = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },
  
  restart: function() {
    if(this.tid) return;
    this.registerCallback();
  },
  
  stop: function() {
    if(!this.tid) return;
    clearInterval(this.tid);
    this.tid = null;
  }
});

var Test = Class.create();
Test.prototype = {
  initialize: function(a) {
    this.a = a;
  },  
  calc: function(b){
    this.a *= b;
    document.getElementById("elem").innerHTML = this.a;
  }
}

var test = new Test(1);

var pe;

function init(){
  pe = new PeriodicalExecuter(test.calc.bind(test, 2), 3);
}
//-->
</script>
</head>
<body onload="init();">
<div id="elem">&nbsp;</div>
<button onclick="pe.restart();">RESTART</button>
<button onclick="pe.stop();">STOP</button>
</body>
</html>

*1:ちなみに、後述するAjax.PeriodicalUpdaterクラスにはstopメソッドがあります。