$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インスタンスを公開することにあります。
メソッド
プロパティ
Promise API
新しいpromiseインスタンスは、deferredインスタンスが作成された際に作成され、 deferred.promiseの呼び出しによって取得することが可能です。
promiseオブジェクトの目的は、タスクが完了した際にdeferredタスクの結果に対し、 関連するものがそれにアクセス出来るようにすることにあります。
メソッド
メソッドチェーン
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 |
型: 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 |
型: 定数、メッセージ、例外、または拒絶理由を表すオブジェクト等を指定します。 |
戻り値 | 説明 |
型: reasonを使用して拒絶とされたpromiseが返ります。 |
when(value)
値、または$qのpromise内で(サードパーティ製)thenが可能かもしれないオブジェクトをラップします。 これは、オブジェクトがpromiseかもしれないし、そうでないかもしれない、またはpromiseが信頼できるものでは無いかもしれない、といった場合に便利です。
引数 | 説明 |
---|---|
value |
型: 値、またはpromiseを指定します。 |
戻り値 | 説明 |
型: 渡された値またはpromiseを、prmiseとして返します。 |
© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.
このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。
- AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
- "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。