Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

遅延関数 その3 + 状態も管理

そもそも状態管理と遅延関数を分けるといいよね。
でFunction.prototyperなわけですよ。

状態管理関数

Function.prototype.state = function(obj){
  var x = this;
  var func = function() {
    func.__state = 2;
    var args = [];
    for(var i = 0; i < arguments.length;i++){
      args.push(arguments[i]);
    }
    if(obj && obj['init']){
      	if(typeof(obj['init']) == 'function'  && func.__state == 1){
      		obj['init'].apply(x,args);
      	}
    }
    if(!func.__cancel){
      if(obj && obj['execute']){
      	if(typeof(obj['execute']) == 'function'){
      		obj['execute'].apply(x,args);
      	}
      }
      var o = x.apply(x, args);
      func.__state = 3;
      if(obj && obj['complete']){
      	if(typeof(obj['complete']) == 'function'){
      		obj['complete'].apply(x,args);
      	}
      }
      return o;
    }
  }
  func.prototype.__cancel = false;
  func.prototype.__state = 1;
  func.cancel = function(c){
    func.__cancel = c;
  }
  func.origin = x;
  return func;
}

ふーむ、ちょっと長いけど・・・・。
__stateで実行中かどうかstateを判断できます。
stateは3つ

  • 1:初期化
  • 2:実行中
  • 3:完了

初期化はホントに最初だけ。あとは2,3を繰り返します。
状態管理を行う理由は

  • ダブルサブミット問題をなんとかするため

これはAjaxでもダブルサブミット問題はあります。
特にAjaxを使い始めるとイベントの嵐が発生しかねないのでなるだけ余計な処理をさせないようにした方が良いでしょう。

if(func.__state == 2){
  //実行中ならばキャンセル
}else{
 //実行
  func()
}

のように出来ます。
またそれらに対し、コールバック関数をセットできます。
stateに合わせ

  • 1:init
  • 2:execute
  • 3:complete

という感じです。

var obj = {
  called : false,
  complete:function(){
    this.called = false;
  }.bindScope(this)
}
func.state(obj);

のように終了後、フラグを下ろしたり等に使えます。
状態管理はこいつに任せたので遅延関数はスッキリします。

遅延関数

Function.prototype.deferred = function(time){
  var x = this;
  var timeout;
  var args = [];
  
  var exec = function() {
	  var o = x.apply(x, args);
      clearTimeout(timeout);
      return o;
  }
  
  var func =  function(){
    for(var i = 0; i < arguments.length;i++){
      args.push(arguments[i]);
    }
    timeout = setTimeout(exec, time);
  }
  
  func.origin = x;
  func.clear = function(){
  	if(timeout){
  		clearTimeout(timeout);
  	}
  }
  return func;
}

clearで遅延を止める事ができます。
多分一般的には連鎖で使うイメージかなと。

var func2 = func.state().deferred(300);
func2.origin.cancel(true);

連鎖でもoriginプロパティで元ネタをたどることができるので上記のようなキャンセルも可能です。
FireFoxでしか動かないけど簡単なサンプルです。
ダブルクリックか判断してそれにあわせたメソッドを呼びます。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-31j">

<title>Click Sample</title>
<script language="JavaScript" src="../js/kumu/kumu.js"></script>
<script type="text/javascript">

var singled = false;
var obj = {
  called : false,
  complete:function(){
    this.called = false;
  }.bindScope(this)
}

function clicks(singleFunc, doubleFunc){
  if(obj.called){    
    singled.clear();
    obj.called = false;
    doubleFunc();
  }else{
    if(singled){
      singled.clear();
    }
    singled = singleFunc.state(obj).deferred(300);
    singled();
    obj.called = true;
  }
}

function single(){
  alert('single');
}

function double(){
  alert('double');
}

</script>

</head>
<body>
<input type="button" value="Click" onclick="clicks(single, double);">
</body>
</html>

ふーむ。難しいね。
うくく。