非同期のJavaScript: Promise

以前このブログでは、Promise について書きました。 (約束の地) その時には、 wilk/Ext.ux.Deferred: Promises for ExtJS 4 and Sencha Touch というライブラリを紹介しました。

そのPromiseですが、Ext JS 6 ではサポートされるようになったのはご存じでしょうか? Ext.Promise – Ext JS – Sencha Docs

そのPromiseについて、SenchaのLee Boonstraがブログ記事 (Asynchronous JavaScript: Promises) を書いてくれたので翻訳しました。ご覧ください。

JavaScript はシングルスレッドです。 上から下に順番にコードを実行します。 ですから、同時に二つのコードを走らせることはできません。 例えば、JSON ファイルを(外部の)サーバーからダウンロードする時には、そのファイルを取得するまで待たなければなりません。 非同期のJavaScriptを使って、スレッドを止めずに、効率的にコードを実行する方法があります。

皆さんは恐らく非同期のJavaScriptのことをよくご存じのことと思います。 イベント(Observerパターン)とコールバックは非同期コードの一例です。 例えば、Ext.Ajax リクエストを送ったり、ユーザーがボタンを押した際には、アクションがイベントループと呼ばれるキューにプッシュされます。 JavaScriptエンジンは、(上から順に)ある非同期の関数の実行が終了するまでは、 イベントループの処理を開始しません。 このせいで、JavaScriptはマルチスレッドではないのに、そのように見えることになります。

Event Loop

コールバックは、非同期操作をする (例えば、データベースからデータをロードするなど) 際に、その完了をユーザーに通知しなければならない時によく使われます。

そういった関数を呼び出すときに、引数として動作を確認するためのもう一つの関数を渡します。 コールバックをコールバックの中に入れることはすばらしい解決案ですし、私はそれがJavaScriptの強力な特徴の1つであると思います。 しかし、最初のコードにさらに非同期処理が必要となったときに、コードが汚くなってしまうかもしれません。

たとえば、ローカルストレージから、ユーザー設定を読み込みました。(それはコールバックです!) それらの設定に基づいて、データベースに接続した外部サーバーにリクエストします。(コールバック #2) 画面に情報を描画する前に、何らかのものをデータベースから取り出します。 (コールバック#3)

大規模な企業アプリを構築する際には、これらのステップは非常に一般的です。 コールバック関数の中にコールバック関数が書かれ、それは別のコールバック関数の中にあります。 こういった関数が複数のファイルに散らばってしまった時に、 1ヵ月後に、このコードを読むのにどれほど苦労するかは想像に難くありません。

ここがJavaScriptのPromiseの使いどころです。 それは、より読みやすく分かりやすいやり方でコードを書く新しい方法です。 Promiseとはタスクの結果を意味します。そして、その結果は完了している場合もあるし、していない場合もあります。 Promiseがつくられるときにはどうなっているかわからない値に契約するようなものです。 それは then メソッドを持ったオブジェクトまたは関数です。 “then” メソッドのおかげで、アクションをエンドレスに繋げることができます。素晴らしい!

Promise には4つの状態があります。

  • fulfilled – Promise が成功した時
  • rejected – Promise が失敗した時
  • pending – 処理中。まだ filfilledにもrjectedにもなっていない
  • settled – すでに fulfilled または rejected になっている

JavaScript Promises

Promise は ECMAScript6 の一部でモダンブラウザーの一部や Node.js の最新版で利用できます。 次のブラウザーでは、まだ JavaScript Promise はサポートされません。 IE11、Android 4.4 以前、iOS Safari 7.1 以前

Ext JS 6 は、Promise をサポートし、Promise A+ スペックに準拠します。 Classic / Modern の両方のツールキットに含まれます。 ネイティブJavaScriptのラッパーである Sencha Promise クラスを呼び出すことができます。 レガシーブラウザーでは、Sencha が提供する機能が使われ、モダンブラウザーではネイティブの機能が使われます。

次は、Sencha Promise オブジェクトを返す関数の例です。

[js] requestUserSettings: function(){ return new Ext.Promise(function (resolve, reject) { //something asynchronous, like loading a store Ext.getStore(‘Settings’).load({ callback: function(records, operation, success) { if(success){ if(records.length > 0){ //when it’s ok resolve(records); } else { //still ok, but no results resolve(false); } } else{ //something bad happened reject(operation); } } }); }); } [/js]

Ext.Promise コンストラクターは、引数を一つ取ります。 それはresolveとrejectの2つの引数を持つコールバックです。 コールバック内で ローカルストレージからユーザー設定を取り出すなどのような 非同期処理が実行され、 結果が返されると “resolve“が呼び出され、 そうでなければ “reject“が呼び出されます。 ます。

次が、このPromiseの使い方です。

[js] this.requestUserSettings().then(function(records) { //It’s ok. do something with the records }, function(err) { //oh no, something went wrong, display a nice error }); [/js]

then” は2つの引数を取ります。成功した場合と失敗した場合のコールバックです。 両方とも必須ではありませんので、成功か失敗のどちらかのコールバックだけをセットすることもできます。 沢山の thenメソッドをチェーンして次々に非同期処理を繋げることができます。

“then()”インスタンスメソッドだけでなく、Promise チェーンを終わらせたり(“done()”)、キャンセルしたり(“cancel()”)する方法もあります。また、チェーンにonCompletedコールバックをアタッチする方法もあります。(“always()”)例えば、結果にかかわらずクリーンナップロジックを実行する時などに使います。チェーン内のアクションがリジェクトされた時にonRejectedコールバックをアタッチすることもできます。(“otherwise()”)

Sencha には Ext.Promise に加えて、 Ext JS 6 内でPromiseを生成するために使う Ext.Deferred も搭載されています。 これらの2つのコンストラクターの違いは、Deferredコンストラクターでは、コンストラクターが、進捗状況の更新のような、「舞台裏」に直接アクセスするという点です

最後になりましたが、Sencha は Ext.Ajax でPromiseのサポートを統合しました。

Ext.Ajax.request()は、 then メソッドを使うことができる Ext.data.request.Base から派生したクラスのインスタンスです。 これにより次のように記述することができます。

[js] Ext.Ajax.request({ url: ‘feed.json’, }).then(function(response) { // use response }).always(function() { // clean-up logic, regardless the outcome }).otherwise(function(reason){ // handle failure }); [/js]

参考文献

JavaScriptやExt JS Promise の使い方を説明した情報が沢山あります。 次の資料をご覧になって勉強して下さい。