jQuery.Deferredとは
jQueryバージョン1.5から加わった機能で、非同期処理を便利に行うために用意されたものです。
$.ajaxなどをサンプルを見ると、書き方が変わったことが分かります。
jQuery バージョン1.5より前
$.ajax({ url: 'data.html', success: function(data) { console.log('success'); }, error: function(data) { console.log('error'); } });
jQuery バージョン1.5以降
$.ajax( 'data.html' ) .done(function() { console.log('success'); }) .fail(function() { console.log('error'); });
同期と非同期
同期・非同期とは何でしょう?
同期は、処理を実行したら、結果・完了が返ってくるのを待って、次の処理を実行します。
非同期は、処理を実行したら、結果・完了を待たずに、次の処理を実行します。
特にJavaScriptでは、非同期で処理が行われる場合が多く、コールバックを多用して、次の処理を実行します。
コールバック地獄
非同期での処理が多くなると、コールバックによって入れ子が深くなり、コードの視認性・可読性が悪くなり、あまり良くありません。
成り行きで、入れ子の深いコードを書くことは、簡単ですが、自分で書いたコードでも、二度と触りたくないものになることがあります。
ECMAScript6で実装されるyield(イールド)というものを使うと、このコールバックの地獄から逃れることができるようですが、ブラウザでの実装が追いついていない状況です。
というわけで、jQuery.Deferredを使って、非同期処理を書こうと思います。
とりあえずアニメーションでやってみた
簡単なアニメーションでは、あまり恩恵は得られませんが、やってみました。
jQueryのanimateとcompleteを利用して、順番に動かします。
普通にやるとこんな感じでしょうか。
コールバックを入れ子
function animeTest1(){ var $target = $('#wrapper>p'); $target.eq(0).animate({'height':'1em','top':'10px'},1000,function(){ $target.eq(1).animate({'left':'10px','top':'90px'},1000,function(){ $target.eq(2).animate({'left':'10px','top':'120px'},1000,function(){ $target.eq(3).animate({'top':'180px'},1000,function(){ $target.eq(4).animate({'left':'10px','top':'260px'},1000,function(){ $target.eq(5).animate({'left':'10px','top': '300px'},1000,function(){ alert('完了'); }); }); }); }); }); }); } $(document).ready(function(){ animeTest1(); });
deferredを使ったパターン1
function animeTest2(){ var $target = $('#wrapper>p'); var promiseFunc = function(action){ var d = $.Deferred(); action(d); return d.promise(); }; promiseFunc(function(d){ $target.eq(0).animate({'height':'1em','top':'10px'},1000,d.resolve); }).then(function(){ return promiseFunc(function(d){ $target.eq(1).animate({'left':'10px','top':'90px'},1000,d.resolve); }); }).then(function(){ return promiseFunc(function(d){ $target.eq(2).animate({'left':'10px','top':'120px'},1000,d.resolve); }); }).then(function(){ return promiseFunc(function(d){ $target.eq(3).animate({'top':'180px'},1000,d.resolve); }); }).then(function(){ return promiseFunc(function(d){ $target.eq(4).animate({'left':'10px','top':'260px'},1000,d.resolve); }); }).then(function(){ return promiseFunc(function(d){ $target.eq(5).animate({'left':'10px','top': '300px'},1000,d.resolve); }); }).always(function(){ alert('完了'); }); } $(document).ready(function(){ animeTest2(); });
行数的には長くなりました。あまりイケていません。
調べてみると、ajaxだけでなく、animateなどもdeferredオブジェクトが使われているようです。
わざわざ、自分で$.Deferred()しなくても良いようです。
deferredを使ったパターン2
function animeTest3(){ var $target = $('#wrapper>p'); $target.eq(0).animate({'height':'1em','top':'10px'},1000).promise() .then(function(){ return $target.eq(1).animate({'left':'10px','top':'90px'},1000); }).then(function(){ return $target.eq(2).animate({'left':'10px','top':'120px'},1000); }).then(function(){ return $target.eq(3).animate({'top':'180px'},1000); }).then(function(){ return $target.eq(4).animate({'left':'10px','top':'260px'},1000); }).then(function(){ return $target.eq(5).animate({'left':'10px','top': '300px'},1000); }).always(function(){ alert('完了'); }); } $(document).ready(function(){ animeTest3(); });
先ほどよりもスッキリしましたね。
これなら実務でも簡単に使えそうです。
$.when
$.whenを使うと、複数のdeferredオブジェクトを監視することができます。
whenを使ったパターン
function animeTest4(){ var $target = $('#wrapper>p'); function anime1(){ return $target.eq(0).animate({'height':'1em','top':'10px'},1000).promise() .then(function(){ return $target.eq(1).animate({'left':'10px','top':'90px'},1000); }).then(function(){ return $target.eq(2).animate({'left':'10px','top':'120px'},1000); }); } function anime2(){ return $target.eq(3).animate({'top':'180px'},1000).promise() .then(function(){ return $target.eq(4).animate({'left':'10px','top':'260px'},1000); }).then(function(){ return $target.eq(5).animate({'left':'10px','top': '300px'},1000); }); } $.when( anime1(), anime2() ).done(function(){ alert('完了'); }); } $(document).ready(function(){ animeTest4(); });
まとめ
jQuery.Deferredは、deferredオブジェクトを作成(もしくはdeferredオブジェクトが作成される機能を使用)し、promiseを返すとdeferredを使う準備ができます。
準備後は、thenを使って、チェーンメソッドで、処理を次々実行させることができます。
thenは自動でpromiseを返すので、thenを連結させていくと、簡単に記述できます。
jQueryのanimate系は、アニメーション終了時に自動的にresolveしてくれるようで、こちらも意識しなくても良いようになっています。
これで、非同期ライフが送れそうです。
そんじゃーねー。
コメント