necotech blog

非同期処理(アニメーションとかAjaxとか)のロック機構

非同期な処理を実行する関数がある場合、その関数を重複して実行させたくない場合が多々あります。こういう場合は、ロック用フラグを1つ用意して、ロックされている時に早期リターンしてやれば大体の場合はうまくいきます。この処理の実装を、チュートリアル風にまとめてみました。

上記サンプルの[ロックあり/なし]ボタンを連打するとその違いが確認できます。ロックの実装だけを抜粋すると、下記になります。

[追記2017/03/06] サンプルが消えていたので修正しました。

  var locked = false;
$("#button2").on("click", function(){
  if(locked) return;
  locked = true;

  block2.animate({left: parseInt(block2.css("left"))+30}, function(){
    locked = false;
  });
});

ロック機構を抽象化する

このパターンは、アニメーションに関わらず非同期処理ならどこでも適用できるので、一つの関数として用意して使い回すことができます。

    var locked = false;

var with_lock = function(fn){
  if(locked) return;
  locked = true;

  fn(function(){locked = false;});
};
  

この関数を冒頭のサンプルに適用すると、下記のようになります。

    $("#button2").on("click", function(){
  with_lock(function(unlock){
    block2.animate({left: parseInt(block2.css("left"))+30}, unlock);
  });
 });
  
[問題点1] 変数lockedが隠蔽されていない

上のwith_lock関数では、内部でしか使っていないlockedという変数がグローバルに定義されているので、これを関数内部のローカルな変数として持たせると、下記のようになります。

      var with_lock = (function(){
  var locked = false;

  return function(fn){
    if(locked) return;
    locked = true;
    fn(function(){locked = false;});
  };
})();
    

即時関数でクロージャを作り、そのスコープ内で全て解決できるようにしています。クロージャスコープについては、JavaScriptでの開発に欠かせない概念であり、入門記事も大量にあるので、もし理解が曖昧な方はぜひ調べてみてください。また、実例と合わせてこの辺りの知識を体系的に学びたい場合は、JavaScript: The Good Partsという書籍がオススメです。

[問題点2] 非同期処理が複数ある場合に、関数を使いまわせない

ここまでのwith_lock関数にはロックを管理するフラグが1つしかないため、複数の非同期処理に対して別々に適用したい場合はうまくいきません。これらを解消する方法としては、フラグを配列で管理し、それぞれのロック処理に任意の名前を付けるようにできれば解決できます。分かりづらいので具体的なコードを書くと、下記のようになります。

      var with_lock = (function(){
  var locks = {};

  return function(domain, fn){
    if(locks[domain]) return;
    locks[domain] = true;
    fn(function(){locks[domain] = false;});
  };
})();
    

with_lockの第一引数に、非同期処理の名前を渡せるようにしています。下記のように使用できます。

      $("#button1").on("click", function(){
  with_lock("animation1", function(unlock){
    block1.animate({left: parseInt(block1.css("left"))+30}, unlock);
  });
 });

$("#button2").on("click", function(){
  with_lock("animation2", function(unlock){
    block2.animate({left: parseInt(block2.css("left"))+30}, unlock);
  });
 });

$("#get").on("click", function(){
  with_lock("ajax", function(unlock){
    $.get("path/to/resource", function(data){
      console.log(data);
      unlock();
    });
  });
});
    
ロックの共有

さて、ここまでで非同期処理の数に関係なく動作するロック機構が完成しましたが、上記の非同期処理の名前を利用すると、複数の非同期処理間であえてロックを共有するようなこともできます。たとえば、一般的なスライドショーを実装する場合は、[戻る/進む]ボタンやインジケータや自動スライドなどで、「スライドショー全体のロック」が必要になってきます。with_lockを使い、それを実行したものが下記です。

[追記2017/03/06] サンプルが消えていたので修正しました。

with_lock.js (github)

Share on Facebook
Share on LinkedIn
Pocket