« 2007年07月 | メイン | 2007年09月 »

2007年08月 アーカイブ

2007年08月13日

JavaScriptのsetTimeoutとsetIntervalをラッピングし、使い勝手を統一したLibrary

JavaScriptには遅延実行を行うためのメソッドが二つ存在する。
windowオブジェクトに属するsetTimeoutとsetIntervalである。
setTimeoutは一定時間後に処理を、setIntervalは一定時間間隔で処理を実行させる。
つまり、同じ処理を何度も繰り返す場合、大きく二通りのやり方が考えられる。
textareaに乱数を書き出す(setTimeoutの場合)
function timer(){
    document.getElementById("output").value+=Math.random()+"\n";
    setTimeout(timer,1000);
}
timer();
textareaに乱数を書き出す(setIntervalの場合)
function timer(){
    document.getElementById("output").value+=Math.random()+"\n";
}
setInterval(timer,1000);
上記例では、ほとんど問題にならないが、両者では処理にかかる時間によって出力の間隔が若干異なり、
setTimeoutは「固定遅延実行」、setIntervalは「固定頻度実行」となる。
ほとんど同じ処理にもかかわらず、「固定遅延実行」と「固定頻度実行」の違いだけで、記述方法を変えなければならないのはなんとも面倒だ。
両者の違いを吸収し、使い勝手を統一するLibraryを作成した。
/*
================================================================================
    Name        :   TimerUtility
    In          :   [none]      
    Out         :   [none]      
    Note        :   タイマー用ユーティリティ群
--------------------------------------------------------------------------------
    Version     :   Ver0.1.0    |   2007/08/12  |   新規作成
--------------------------------------------------------------------------------
    License     :   MIT license
    URL         :   www.kanasansoft.com
================================================================================
*/

/*--------------------------------------------------------------------------------
    コンストラクタ
--------------------------------------------------------------------------------*/
function TimerUtility(){
}

TimerUtility.Timeout
=   function(arg){
    this._ontimer       =   arg["ontimer"]      ;
    this._count         =   arg["count"]        ;
    this._msec          =   arg["msec"]         ;
    this._onend         =   arg["onend"]        ;
    this._onstop        =   arg["onstop"]       ;
    this._counter       =   0                   ;
    this._result        =   []                  ;
}

TimerUtility.Interval
=   function(arg){
    this._ontimer       =   arg["ontimer"]      ;
    this._count         =   arg["count"]        ;
    this._msec          =   arg["msec"]         ;
    this._onend         =   arg["onend"]        ;
    this._onstop        =   arg["onstop"]       ;
    this._counter       =   0                   ;
    this._result        =   []                  ;
}

/*--------------------------------------------------------------------------------
    TimeoutのClosure
--------------------------------------------------------------------------------*/
TimerUtility.Timeout._closure=function(arg){
    var _scope          =   arg["scope"]        ;
    var _arg            =   arg["args"]         ;
    return function(){_scope.start.apply(_scope,_arg)};
}

/*--------------------------------------------------------------------------------
    Timeoutの開始処理(実行毎に呼び出される)
--------------------------------------------------------------------------------*/
TimerUtility.Timeout.prototype.start
=   function(){
    if(this._counter<this._count){
        this._counter++;
        var result=this._ontimer.apply(this,arguments);
        this._result.push(result);
        if(this._onend){
            this._onend(result);
        }
        this._id=setTimeout(
            this.constructor._closure(
                {   "scope"     :   this                ,
                    "args"      :   arguments           }
            )
            ,this._msec
        );
    }else{
        this.stop();
    }
}

/*--------------------------------------------------------------------------------
    Timeoutの終了処理
--------------------------------------------------------------------------------*/
TimerUtility.Timeout.prototype.stop
=   function(){
    clearTimeout(this._id);
    if(this._onstop){
        this._onstop(this._result);
    }
}

/*--------------------------------------------------------------------------------
    IntervalのClosure
--------------------------------------------------------------------------------*/
TimerUtility.Interval._closure=function(arg){
    var _scope          =   arg["scope"]        ;
    var _arg            =   arg["args"]         ;
    return function(){
        if(_scope._counter<_scope._count){
            _scope._counter++;
            var result=_scope._ontimer.apply(_scope,_arg);
            _scope._result.push(result);
            if(_scope._onend){
                _scope._onend(result);
            }
        }else{
            _scope.stop();
        }
    };
}

/*--------------------------------------------------------------------------------
    Intervalの開始処理(開始時に呼び出される)
--------------------------------------------------------------------------------*/
TimerUtility.Interval.prototype.start
=   function(){
    this._id=setInterval(
        this.constructor._closure(
            {   "scope"     :   this                ,
                "args"      :   arguments           }
        )
        ,this._msec
    );
}

/*--------------------------------------------------------------------------------
    Intervalの終了処理
--------------------------------------------------------------------------------*/
TimerUtility.Interval.prototype.stop
=   function(){
    clearInterval(this._id);
    if(this._onstop){
        this._onstop(this._result);
    }
}
使用例は以下の通り。
<html>
<head>
<script src="TimerUtility.js" type="text/javascript"></script>
<script>
<!--
var to;
var iv;
function rand(str){
    var dtm=(new Date()).getTime();
    while((new Date()).getTime()<dtm+800){}     //時間のかかる処理のダミー
    return str+" : "+Math.random();
}

function end(num){
    document.getElementById("output").value+=new Date()+" : "+num+"\n";
}

function stop(ary){
    alert(ary.join("\n"));
}
function initial(){
    initialto();
    initialiv();
}
function initialto(){
    to=new TimerUtility.Timeout({"ontimer":rand,"onend":end,"onstop":stop,"count":10,"msec":1000});
}
function initialiv(){
    iv=new TimerUtility.Interval({"ontimer":rand,"onend":end,"onstop":stop,"count":10,"msec":1000});
}
function startto(){
    to.start("timeout");
}
function startiv(){
    iv.start("interval");
}
function stopto(){
    to.stop();
}
function stopiv(){
    iv.stop();
}
function cleardisplay(){
    document.getElementById("output").value="";
}
window.onload=initial;
-->
</script>
</head>
<body>
<input type="button" onclick="startto()" value="start timeout" />
<input type="button" onclick="startiv()" value="start interval" /><br />
<input type="button" onclick="stopto()" value="stop timeout" />
<input type="button" onclick="stopiv()" value="stop interval" /><br />
<input type="button" onclick="cleardisplay()" value="clear display" /><br />
<textarea id="output" style="width:90%;height:90%;" readonly="readonly" onclick="this.select();"></textarea>
</body>
</html>
インスタンス化の際の引数は以下の通り。
    "ontimer"   :   繰り返し行いたい処理。(必須)
    "onend"     :   ontimerに指定された処理の終了時に呼ばれる。ontimerの戻り値が引数となる。(任意)
    "onstop"    :   指定された回数処理が実行された後、もしくは処理が中断された場合に呼ばれる。それまでに実行された全てのontimerの戻り値(配列)が引数となる。(任意)
    "count"     :   実行したい回数を指定。Number.POSITIVE_INFINITYを指定すれば無限。(必須)
    "msec"      :   実行の間隔を指定する。TimerUtility.Timeoutで使用すれば一定時間経過後に、TimerUtility.Intervalで使用すれば一定時間間隔で処理される。(必須)
メソッドは次の通り。
    start       :   タイマーを開始する。指定した引数は、ontimerにそのまま引数として設定される。
    stop        :   タイマーを停止する。再度startを実行すると、処理が再開される。
以上。
2009/02/04 追記
setTimeoutとsetIntervalの呼び出し方について追記しています。
Google