この記事は、 Sencha Advent Calendar 2013 の12日目の記事です。
ロバート・B・パーカー著「約束の地」より。
パーカーも亡くなっちゃったんですね。
今回はPromiseについてお話しします。
Senchaのフレームワークでは、多くの場面で非同期の処理をする必要があります。 その最たるものがサーバーとの通信です。
failure
がありませんね。ごめんなさい。m(_ _)m
failure
はちゃんと書きましょう。
リクエストの結果は、非同期で実行されるsuccess
関数内で受け取ります。
受け取った結果をもとに次の処理をするときには、当然ながらsuccess
関数内から実行しなくてはなりません。
コード中の process server response here という所で実行するわけですね。
コード上の next step と書かれた場所で何かを実行しても、サーバーからのレスポンスはこの時点では通常は返ってきていません。
複数の非同期処理をチェーンの様に実行したい時ってあります。 そんな時は、こうして関数の中にネストして処理してやると、どんどんネストが深くなって悲しいことになります。
そこで、非同期の処理をシーケンシャルな処理のように書く方法ってないものかなぁ、と思うわけです。 こういう要求にはいろいろなライブラリとかあるんですよね。
CommonJSのPromiseパターンというのがあって、それがどうもこのやり方のこれからの標準のようですね。
Senchaのブログ記事、 Implementing User Extensions for Sencha Architect 3.0 Preview (日本語訳は「 Sencha Architect 3.0 プレビューでのユーザー拡張機能の実装」) では、 深くネストした関数コールバックの解決に、promiseを使うことにした、ということが記述されています。
なんかいいライブラリないかな?
私は、みつけました。
_人人人人人人人人_
> Ext.ux.Deferred <
 ̄Y^Y^Y^Y^Y^Y^ ̄
今日は、このクラスの使い方を勉強してみましょう。
名簿のアプリを例にします。
このように、personsテーブルでは、prefectures(都道府県)、carriers(携帯キャリア)、companies(会社)という3つのマスターを参照しています。これを
このようにGridの中に表示したいとします。
それぞれのテーブルは、Persons/Prefectures/Carriers/Companies というストアに格納され、それぞれがAjaxプロキシでもってサーバーからデータを取得します。
グリッドでは、次のように’renderer’関数を使って表示してやります。
4つのストアは、コントローラーのonLaunchメソッドでロードしましょう。
Personsストアをロードする前にちゃんと他のマスターをロードしてますね。これで大丈夫です。
って、そんなわけないだろ!(`Д´)ノ
こちら、load
メソッドは非同期ですので、マスターのロードが終わっていなくても、Personsストアのロードが始まってしまう可能性があります。
ためしに、マスターのサーバー側メソッドでWaitをかまして、遅くしてやると。
このようにエラーが発生して、カラムにデータが表示されません。
これは、マスターがロードし終わる前にPersonsストアがロードされてしまって、Gridのrenderer
が呼ばれてしまっているからです。
これを Ext.ux.Deferred
ですべてのマスターのストアがロードされてから、Personsストアをロードするようにしてみましょう。
まず、Ext.ux.Deferred
をプロジェクトに追加します。
僕の場合は、 app/ux/Deferred.js に置きました。
Ext.uxというネームスペースなので、そのままだとSDKのソースディレクトリに取りに行ってエラーになるので、app.jsで、Ext.Loaderにパスを教えます。
app以外のところ(classpathが通っていないところ)においた場合は、app.jsonとかsencha.cfgとかでclasspathを設定してやる必要があります。
そして、これを使う、コントローラーの requires に指定してロードさせます。
Ext.ux.Deferred
の使い方は、
こんな風に呼び出します。promiseは、非同期処理をする関数です。resolveはpromiseの非同期処理が完了したら呼び出される関数。rejectは非同期処理が却下されたときに呼び出される関数です。
then
の後に更に、.then
として処理をチェーンすることもできます。
promiseにセットする関数は、Ext.ux.Deferred
のインスタンスを返す関数である必要があります。
async = function() {
var dfd = Ext.create('Ext.ux.Deferred');
asyncFunc(function() {
dfd.resolve();
});
}
このような感じになります。
関数の中でExt.ux.Deferred
のインスタンスを生成して、非同期処理が終わったらそのインスタンスのresolve
メソッドを呼んでやります。
それをExt.ux.Deferred
のスタティックメソッドであるwhen
に渡してやります。
また、when
メソッドに渡すpromise関数は複数指定できます。
Ext.ux.Deferred.when( promise1, promise2, promise3 ).then(resolve[, reject]);
この方法で、3つのマスターをロードして、それらがすべて整ったらPersonsストアをロードするというのを実現できそうです。
ストアをオープンするためのpromise関数は次のような感じになります。
Ext.ux.Deferred
オブジェクトを生成して、load
メソッドのコールバックの中で、resolve
またはreject
を呼び出しています。
このやり方でマスター全部のpromise関数を作ってやります。 出来上がったonLaunchメソッドは次のようになりました。
3行目からのpromise
という関数は、指定されたストアをロードするpromise関数を返します。
この関数を使ってpuromise関数を3つつくってwhen
メソッドに渡しています。
こうすることで、全てのマスターストアがロードされてから、Personsテーブルをロードすることを確実にすることができました。
Ext.ux.Deferred
は、この他にもExt.Directでのサーバーサイド関数呼び出しでのチェーンなんかにも使えますし、コンポーネントテストを書くときに使っても便利ですね。
Senchaフレームワークの標準で、非同期の実行を待つ処理を書けるのは、たしかSencha TouchのRouter機能のbeforeフィルターの中だけだったと思います。
beforeフィルターのように、ある処理をする前に、なにかがどうにかなっているのを確実にするために、このExt.ux.Deferred
を使うと、より安定したアプリケーションを構築できる可能性がありますね。
ピンバック: 非同期のJavaScript: Promise | Sunvisor Lab. Ext JS 別館
こんにちは 困ったらここにたどり着く事が多く、いつも重宝しております。 1か所間違いでは?と思う個所が有りましたのでご連絡いたします。ご確認いただけないでしょうか。
一番最後に記載されているソースの16行目: 「return dfd;」 の部分ですが、 「return dfd.promise();」 の間違いでは無いでしょうか。
GithubのTutorial では、「return dfd.promise();」が記載されていると思われます。
よろしくお願いいたします。
Ext4.1.3 chrome : 56.0.2924.87 (64-bit)
コメントありがとうございます。 この記事の執筆時点では
return dfd
で正しかったのが、ライブラリの方のバージョンアップで仕様が変わったのだと思います。最新の仕様に合わせて記述してください。ご回答ありがとうございます。
履歴の確認を怠っておりました。 失礼いたしました。