$.Deferred()を利用して関数を順番通り実行する【jQuery】

公開日:

最終更新日:

$.Deferred()を利用して関数を順番通り実行する【jQuery】

JavaScriptはコードの上から順に処理をしていくので、特に考えなくても処理は完結すると思います。

ただし、処理の方法によっては先に実行したfunctionの結果を受け取れない場合もあったりします。

そこでdeferredとpromiseを利用することで、意図した順番と結果を受け取るように処理を行いたいと思います。

今回はjQueryを使用して、処理の終了を検知して次の処理に移行する$.Deferred()について書きたいと思います。

JSの処理の流れと遅延実行

実際に処理を遅らせようと思えば、setTimeout()を使えば遅らせることも可能です。
下のようなコードにすれば、開発ツールのコンソールに「1 2 3」と続けて表示できます。

setTimeout(function(){
  console.log('1');
}, 1000);
setTimeout(function(){
  console.log('2');
}, 2000);
setTimeout(function(){
  console.log('3');
}, 3000);

ここまで簡単な処理なら特にほぼ時間通りに処理が実行されると思いますが、もっと複雑な処理の場合や間に別の処理を挟んだ場合、確実に1秒ごとに処理が実行される可能性は低くなります。

jQueryでアニメーションをつける際にはanimate(function, duration, easing, callback)のcallbackの部分でアニメーション終了を検知して、次の処理へ繋ぐことも可能です。
ただし、複雑なアニメーションを実装するとなると入れ子が深くなり、かなり見通しが悪くなります。

jQuery(function($){
  $('.animateElm').animate({'width' : '100px'}, function(){
    $('.animateElm').animate({'width' : '200px'}, function(){
      $('.animateElm').animate({'height' : '100px'}, function(){
        $('.animateElm').animate({'height' : '200px'}, function(){
        });
      });
    });
  });
});

入れ子の親から順番に実行されていくようにはなりますが、複数処理をコールバック内に記述していくとネストが深くなり、かなり見通しは悪くなりますね。
ちなみに、durationとeasingに関しては省略が可能です。

$.Deferredを使った処理

上で書いたようなsetTimeoutを使った不確定な遅延処理や、コールバックを使用したネスト地獄を解決するために$.Deferredを使います。

全て関数を繋げて書いていましたが、変数に代入もしくは関数化してしまいます。
今回は関数化して処理を書きます。

jQuery(function($){
  function count(num){
    num = parseInt(num);
    var dfd = new $.Deferred();
    var delay = (num*1000);
    setTimeout(function(){
      console.log(num);
      dfd.resolve();
    }, delay);
    return dfd.promise();
  }
  count(1).then(count(2)).then(count(3));
});

見た目上は特に何も変わらないかもしれませんが、処理的には1,2,3と順番に実行されていきます。

ここで錯覚しそうな部分と言っては何ですが、あくまでもsetTimeoutは「遅らせて実行する」というだけです。

なので、setTimeoutで指定した秒数より早く処理結果がでるものは先に実行されているような感覚になりますが、記述通り順番に実行されているだけです。

promise()について

promise()は$.Deferred()でresolveかrejectかを受け取り.then()に繋ぐことでコールバック関数を実行できるような仕組みかなといったところです。

.then()の引数は考え方としてはajaxの処理と同じで.then(done, fail)となっていて、doneがresolveの場合でfailがrejectを受け取った場合の処理になります。

サンプルコードで書いている処理でいうと、setTimeoutが通ればresolveとなるので、doneの処理が実行されます。

rejectに関してはエラーが想定される場合に記述しておくと.then()ではfailとして処理されます。
今回は書いていないですが、処理をされなかった場合も想定してalertを出すなどしておく方がより親切ですね。

まとめ

$.Deferred()を知るまではsetTimeoutで処理を繋げたり、.animate()のコールバックを利用したり、ネストがかなり深くなり管理も大変でした。
$.Deferredで実装することによって不確定な秒数設定をして処理を遅延させて実行するよりかなり確実な処理ができるようになり、かつネストも深くならないので管理もしやすくなりました。

最近のGoogleに評価されているサイトの傾向でいうと、setTimeoutやJavaScriptによるレンダリングの遅れは悪とされているので、$.Deferred()で安定的な動作が保証されるようにコーディングした方が良さそうです。

関連記事