$q

概要

promise/deferredの実装は、Kris Kowal's Qにインスパイアされています。

CommonJSによるPromiseの提案には、 promiseは非同期実行動作と与えられた時間内に終えるか否かの結果を表したオブジェクトと、相互に作用し合うインターフェースとすると説明されています。

エラーハンドリング使用の視点から、deferredとpromiseのAPIは非同期プログラミングのための、 同期プログラミングのtry/catch/throwに該当します。

// $qとscopeを現在のスコープで利用可能な変数とみなします。
// (これらは注入か、もしくは渡されています)
function asyncGreet(name) {
  var deferred = $q.defer();

  setTimeout(function() {
    // この関数は、後のイベントループの非同期で実行されるため、
    // コードを$applyの呼び出しにラップする必要があり、モデルの変更に対して適切に監視を行います。
    scope.$apply(function() {
      deferred.notify('About to greet ' + name + '.');

      if (okToGreet(name)) {
        deferred.resolve('Hello, ' + name + '!');
      } else {
        deferred.reject('Greeting ' + name + ' is not allowed.');
      }
    });
  }, 1000);

  return deferred.promise;
}

var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
  alert('Success: ' + greeting);
}, function(reason) {
  alert('Failed: ' + reason);
}, function(update) {
  alert('Got notification: ' + update);
});

初めは、この複雑な拡張に果たして価値があるのか疑問に思うかもしれません。 promiseとdeferredのAPIがそれに見合う対価があることを保証してくれるはずです。

加えてpromiseのAPIは、伝統的なコールバック(CPS)のアプローチを使用した非常に難しい構成を許可します。 これについて更に詳しく知りたければ、Qドキュメントの、 シリアルまたはパラレルによる結合のセクション部分を参照してください。

Deferred API

$q.defer()を呼ぶことで、deferredの新しいインスタンスが作られます。

deferredオブジェクトの目的は、成功/失敗の完了のシグナルが使用できるAPI、タスクのステータスはもちろんのこと、 関連するpromiseインスタンスを公開することにあります。

メソッド
resolve(value)
valueを使用してpromiseを解決します。 もし、valuが$q.rejectを通して「拒絶」とされたものであれば、代わりにpromiseは拒絶されることになります。
reject(reason)
reasonを使用してpromiseを拒絶します。 これは、resolveの際に$q.rejectを通して拒絶したことと同じ結果になります。
notify(value)
promise実行のステータスを更新します。 これは、promiseが解決または拒絶のどちらか一方になる前に、複数回呼びだすことが可能です。
プロパティ
promise – {Promise}
promiseオブジェクトは、このdeferredと一緒に関連付けられます。

Promise API

新しいpromiseインスタンスは、deferredインスタンスが作成された際に作成され、 deferred.promiseの呼び出しによって取得することが可能です。

promiseオブジェクトの目的は、タスクが完了した際にdeferredタスクの結果に対し、 関連するものがそれにアクセス出来るようにすることにあります。

メソッド
then(successCallback, errorCallback, notifyCallback)

thenは、promiseの解決・拒絶に関係なく呼び出され、 successまたはerrorのコールバックのどちらか1つがresultが利用可能になって間もなく非同期で呼び出されます。 コールバックは、resultまたは拒絶のreasonのどちらかの引数を1つ渡されて呼び出されます。 加えて、進行の指示をするためのnotifyコールバックはpromiseが解決または拒絶される前に、 1回も呼び出されないかもしれないし、複数回呼び出されるかもしれません。

このメソッドは、successCallbackまたはerrorCallbackの戻り値を通して、解決または拒絶された新しいpromiseを返します。 また、notifyCallbackメソッドの戻り値を通しても通知されます。 promiseはnotifyCallbackメソッドからは、解決または拒絶することは出来ません。

catch(errorCallback)
promise.then(null, errorCallback)の略記です。
finally(callback)

このメソッドは、最終的な値の変更無しに、promiseの完了または拒絶を監視を可能にしてくれます。 これは、promiseの解決・拒絶のどちらであっても、リソースの開放、またはクリーンアップの実行が必要な際に便利です。 詳細は、promise.finally(callback)の仕様を参照してください。

finallyはJavaScriptの予約語であり、予約キーワードはES3によってプロパティとしてサポートされないため、 このメソッドは、コードをIE8互換にするため、promise['finally'](callback)のように実行する必要があります。

メソッドチェーン

promiseのthenメソッドの呼び出しは、新しいpromiseを返すため、 メソッドチェーンを容易に行うことが可能です。

promiseB = promiseA.then(function(result) {
  return result + 1;
});
// promiseBは、promiseAが解決した直後に解決され、
// その値はpromiseAのresultが+1されたものになります。

これは、任意の長さのチェイン作成を可能とし、他のpromiseと一緒にpromiseの解決が出来るため(更にその決定を延期)、 チェイン内でのある時点でのpromiseの中断/延期の決定を可能にします。 これは、$httpレスポンスのインタセプターのようなAPIの、強力な実装を可能にしてくれます。

Kris KowalのQと$qの違い

主に3つの違いが存在します。

  • $qは、Angular内のng.$rootScope.Scopeスコープモデルの監視メカニズムで平等で、 これはモデルへの解決または拒絶の伝搬が早く、ブラウザの不必要な徐々に消えるようなUIのリペイントを避けることを意味します。
  • $qのpromiseは、Angularのテンプレートエンジンによって見分けられ、 テンプレート内でpromiseをスコープに割り当てて、結果値として扱う事が可能になります。
  • Qは$qより多くの機能を持ちますが、その分ファイル容量も多くなっています。 $qは軽量ですが、非同期タスクで必要とされる共通の重要な機能は全て含まれています。
テスト
it('should simulate promise', inject(function($q, $rootScope) {
  var deferred = $q.defer();
  var promise = deferred.promise;
  var resolvedValue;

  promise.then(function(value) { resolvedValue = value; });
  expect(resolvedValue).toBeUndefined();

  // promiseのresolveのシミュレート
  deferred.resolve(123);
  // 'then'関数は同期呼び出しを取得しないことに注意してください。
  // これは、promise APIは、同期・非同期で呼び出されたかに関わらず、
  // 常に非同期にしたいためです。
  expect(resolvedValue).toBeUndefined();

  // promiseの'then'関数への解決の伝搬には、$apply()を使用します。
  $rootScope.$apply();
  expect(resolvedValue).toEqual(123);
});

依存関係

all(promises)

複数のpromiseを単一のpromiseに結合し、全ての入力されたpromiseが解決した際に、それが解決になります。

引数 説明
promises

型:Array.<Promise>Object.<Promise>

promiseの配列、またはハッシュを指定します。

戻り値 説明
 

型:promise

promise配列/ハッシュ内の同じインデックス/キーのpromiseと対応した各値を使用して解決された単一のpromiseを返します。 もし、promiseのいずれかが拒絶された場合、 同じ拒絶値を使用して、この結果のpromiseも拒絶されます。

defer()

戻り値:Deferred

将来完了するとされるタスクを表すDeferredオブジェクトを作成します。

reject(reason)

reasonを指定して、拒絶とされたpromiseを作成します。 このAPIはpromiseのチェーン内で拒絶として進める際に使用する必要があります。 もし、promiseチェーン内の最後のpromiseを扱う場合は、このことについて気にする必要はりません。

deferred/promiseと、よく知られているtry/catch/throwの振る舞いを比較する場合、 rejectはJavaScriptのthrowとして考えます。 また、もしエラーをpromiseエラーコールバックを通して"catch"し、現在のpromiseからエラーを進めたい場合、 rejectを通して拒絶を返すことによって、エラーを"rethrow"する必要があることも意味します。

promiseB = promiseA.then(function(result) {
  // success: 古い、または新しい結果で、何かを実行し、
  //          promiseBをresolveします。
  return result;
}, function(reason) {
  // error: 可能であればエラーを取り扱い、newPromiseOrValueで
  //        promiseBをresolveし、そうでなければpromiseBへ進めます
  if (canHandle(reason)) {
   // エラーとリカバーを行います
   return newPromiseOrValue;
  }
  return $q.reject(reason);
});
引数 説明
reason

型:*

定数、メッセージ、例外、または拒絶理由を表すオブジェクト等を指定します。

戻り値 説明
 

型:Promise

reasonを使用して拒絶とされたpromiseが返ります。

when(value)

値、または$qのpromise内で(サードパーティ製)thenが可能かもしれないオブジェクトをラップします。 これは、オブジェクトがpromiseかもしれないし、そうでないかもしれない、またはpromiseが信頼できるものでは無いかもしれない、といった場合に便利です。

引数 説明
value

型:*

値、またはpromiseを指定します。

戻り値 説明
 

型:Promise

渡された値またはpromiseを、prmiseとして返します。

 Back to top

© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.

このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。