jQuery.Deferred学習メモ(1)$.DeferredでPromiseを操作する

2013-09-11

旧ブログ

t f B! P L

これまで$(window).loadを使って処理していたことが、Promiseを使えばできそうなことに気づきました。Promiseとそれを実装したQuery.Deferredについて調べてみました。

Promiseで処理が完了するまで次の命令の実行を保留にしておく


PromiseはJavaScriptの仕様として決定したものではなくまだ「提案」の段階のようで、AのほかにB,KISS,C,Dがあるようですが、jQeury.DeferredではAが採用されているのでAを使いま す。
A promise represents the eventual value returned from the single completion of an operation. A promise may be in one of the three states, unfulfilled, fulfilled, and failed. The promise may only move from unfulfilled to fulfilled, or unfulfilled to failed.
Promises/A - CommonJS Spec Wiki
これが定義の抜粋です。Promiseは操作の結果を表し、unfulfilled(未完), fulfilled(完了),  failed(失敗)のいずれかの値をとります。未完→完了未完→失敗、という変化しかできません。

どんな場面でPromiseが役立つのでしょう?

JavaScriptの命令は上から順に読み込まれます。

ところが結果の出力に時間がかかる命令があったとしても、その結果を待たずに次の命令を実行します。

処理を並行しても問題ないものであれば、これは時間短縮となり長所になります。

しかし結果を受けて処理する命令の場合は、結果がでるまで次の命令の実行を保留しておく手段が必要です。

その「命令の実行を保留しておく」目印としてPromiseを「栞(しおり)」として使います。

結果を待つ命令にPromise(unfulfilled=未完)を設定して実行を保留にしておき、次の命令に移ります。

待たせていた命令は結果を出したらPromiseをunfulfilled(未完)からfulfilled(完了)またfailed(失敗)へ変更して返します。

それを受けて栞をつけた部分から保留していた命令が実行されます。

実際のJavaScriptのコード例はScript Junkie | Creating Responsive Applications Using jQuery Deferred and Promisesに詳しいです。

以下引用です。
promise = callToAPI( arg1, arg2, ...);//Promiseを返す処理。引数(以下futureValue)も返せる。
promise.then(function( futureValue ) {//Promiseが完了のときに実行。
  /* 何か処理 */
});
promise.then(function( futureValue ) {//Promiseの値は変化しないのでこれも実行される。
  /* 他にまた処理 */
});
失敗のときの処理はpromise.thenの2つ目の引数として実行します。
promise.then(
    function( futureValue ) {//完了のときに実行。
        /* 完了のときの処理 */
    },
    function( futureValue ) {//失敗のときに実行。
        /* 失敗のときの処理 */
    }
);
複数のPromiseの結果を受けるときはwhenでくくるとand条件になります。
when(
    promise1,//Promiseを返す処理1
    promise2,//Promiseを返す処理2
        ...
).then(function( futureValue1, futureValue2, ... ) {//
    /* whenの中のPromiseが全て完了のときに実行。 */
});
さあ、私がやりたいのはfleXcrollのスクロールバーをSyntaxHighlighterに使いたいことなので、このPromiseを使ってSyntaxHightlighterの処理が終わったらPromiseをfulfilledで返して、tableの高さを設定して、fleXcrollを適用すればよいはず、、、

と、思ったらPromiseは概念だけなのでJavaScript自体には実装されていないようです。

とういうことでPromiseを実装したjQuery.Deferredについて今度は調べます。

Promise自体が抽象的なものなので動作を理解するのに結構時間がかかってしまいました。

jQuery.DeferredでPromiseの概念を実装


jQuery1.5でPromiseは機能を追加して、Deferredというオブジェクトで実装されました。

DeferredオブジェクトはPromiseを内包していて、Deferredオブジェクトがpendingresolvedrejectedの各状態のとき、中のPromiseはそれぞれunfulfilled(未完)fulfilled(完了)failed(失敗)になっています。

Deferredオブジェクトは$.Deferred()メソッドで生成できます。
var df1=$.Deferred();
これでdf1というDeferredオブジェクトが生成できます。

このときdf1の状態はpendingであり、中のPromiseはunfulfilled(未完)です。

df1.resolve( [args ] ) これでdf1はresolved(Promiseはfulfilled(完了))に変更されます。

[args]は引数で、続くdone()やfail()などのメソッドに引数を渡せます。

df1.reject( [args ] ) これはdf1をrejected(Promiseはfailed(失敗))に変更します。

df1の中のPromiseは、df1.always(), df1.done(), df1.fail(), df1.then()で受け取れます。

df1.always()はPromiseがunfulfilled以外のときに実行されます。

df1.done()はPromiseがfulfilledのとき、df1.fail()はfailedのときにそれぞれ実行されます。

df1.then()はPromiseの概念のときと同じく、df1.then(fulfilledのときの処理, failedのときの処理)で使えますがjQuery1.5、jQuery1.7、jQuery1.8以降で少しずつ動作が違います。

jQuery1.7からdf1.pregress()が追加されました。これはPromiseがunfulfilledの間df1.notify()で呼び出せます。

Promiseを受け取る4つのパターンの例


BloggerのHTMLモードで貼り付けて動作を見てみます。

テストコード①
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<button id="btn1">未完</button><button id="btn2">完了</button><button id="btn3">失敗</button><br />
<div id="df_ex"></div>
<script>
var df1 = $.Deferred();
df1
  .progress(function(n) {
    $( "#df_ex" ).append(df1.state()+n+"<br/>");
  })
  .done(function(n) {
    $( "#df_ex" ).append(df1.state()+n+"<br/>");
  })
  .fail(function(n) {
    $( "#df_ex" ).append(df1.state()+n+"<br/>");
  })
  .always(function() {
    $( "#df_ex" ).append(df1.state()+"で.always実行"+"<br/>");
  });
$( "button#btn1" ).on( "click", function() {
  df1.notify("で.progress実行");
});
$( "button#btn2" ).on( "click", function() {
  df1.resolve("で.done実行");
});
$( "button#btn3" ).on( "click", function() {
  df1.reject("で.fail実行");
});
</script>
df1.state()はdf1がpending、resolved、rejectedのいずれの状態なのかを教えてくれます。

「未完」ボタンをクリックすると20行目が実行されてunfulfilledのPromiseに引数「で.progress実行」がつけられます。

これを受けて7行目の.progress()が実行されます。


「完了」ボタンをクリックすると23行目が実行されてdf1のPromiseがfulfilledになり、さらに引数「で.done実行」がつけられます。

これを受けて10行目の.done()と16行目の.always()が実行されます。


「失敗」ボタンをクリックすると26行目が実行されてdf1のPromiseがfailedになり、さらに引数「で.fail実行」がつけられます。

これを受けて13行目の.fail()と16行目の.always()が実行されます。


ボタンをクリックしてPromiseの状態を変化させるこの例ではわざわざDeferredを使う必要性を感じないと思います。

「ボタンをクリックされるのを待つ」=「命令の結果を待つ」と考えればよいと思います。

実用するときには「ボタンをクリック」の部分を「命令の結果を受ける」に置き換えればよいのです。

df1.progress()以外は実行されるのはPromiseが変化したときの1回きりのみの


テストコード①で「未完」ボタンをクリックした後「完了」ボタンをクリックすると、pendingの結果にresolvedの結果が追加されます。


ところが「完了」ボタンをクリックした後にはそれ以上ボタンをクリックしても、結果が追加されることはありません。

「完了」「失敗」ボタンが動作するのは1回きりです。

これはdf1.always(), df1.done(), df1.fail(), df1.then()はdf1のPromiseが変化したときだけに呼び出されるからです。

df1.progress()はPromiseがunfulfilledの間は何回でも呼び出されるため、「未完」ボタンだけをクリックしている間はどんどん結果が追加されます。


一度変化したPromiseはもう変化することはないため、「完了」「失敗」ボタンをクリックした後また動くようにするためにはページをリロードするしかありません。

Promiseを逆方向に変化させるメソッドはありません。

参考にしたサイト


Promises/A - CommonJS Spec Wiki
PromiseにはA以外にB,KISS,C,DがありますがAが実装されているようです。

Deferred Object | jQuery API Documentation
Promiseを実装したjQuery.Deferredの本家の解説。各項目をクリックすると例もあります。

サンプルで分かる! jQuery.Deferredで非同期処理入門 - miniapp
私はこれを読んでようやくjQuery.Deferredの概略がつかめました。

jQuery.Deferred をもう少し理解する | Foreignkey, Inc.
次にこれを読んで理解を深めました。

Script Junkie | Creating Responsive Applications Using jQuery Deferred and Promises
英語ですがPromiseとjQuery.Deferredについて詳しく説明されています。

jQueryのDeferredとPromiseで応答性の良いアプ リをー基本編 | ゆっくりと…
上の英語ページの翻訳と解説です。補足で図も追加してあり、わかりやすいです。

とほほのjQuery入門
Promiseを扱うjQuery.Defferedについての解説です。

【Promise、Deferred】 jQuery入門道場
PromiseとjQuery.Deferredを対比して書かれています。

次の関連記事:jQuery.Deferred学習メモ(2)jQuery1.8以降のdeferred.then()を使う

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ