これはあなたの質問に対する完全な答えにはなりませんが、サービスのドキュメントを読み込もうとするときに、これがあなたや他の人に役立つことを願ってい$q
ます。それを理解するのに少し時間がかかりました。
少しの間AngularJSを脇に置いて、FacebookAPI呼び出しについて考えてみましょう。どちらのAPI呼び出しも、コールバックメカニズムを使用して、Facebookからの応答が利用可能になったときに呼び出し元に通知します。
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
これは、JavaScriptやその他の言語で非同期操作を処理するための標準パターンです。
このパターンの大きな問題の1つは、一連の非同期操作を実行する必要がある場合に発生します。この場合、連続する各操作は前の操作の結果に依存します。それがこのコードが行っていることです:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
最初にログインを試み、次にログインが成功したことを確認した後にのみ、GraphAPIにリクエストを送信します。
2つの操作を連鎖させるだけのこの場合でも、物事は厄介になり始めます。メソッドaskFacebookForAuthentication
は失敗と成功のコールバックを受け入れますが、FB.login
成功したがFB.api
失敗した場合はどうなりますか?success
このメソッドは、メソッドの結果に関係なく、常にコールバックを呼び出しますFB.api
。
ここで、各ステップでエラーを適切に処理し、数週間後に他の人やあなたにも判読できるように、3つ以上の非同期操作の堅牢なシーケンスをコーディングしようとしていると想像してください。可能ですが、これらのコールバックをネストし続けて、途中でエラーを追跡できなくなるのは非常に簡単です。
それでは、Facebook APIを少し脇に置いて、サービスによって実装されるAngularPromisesAPIについて考えてみましょう$q
。このサービスによって実装されるパターンは、非同期プログラミングを線形の一連の単純なステートメントに似たものに戻そうとする試みであり、途中の任意のステップでエラーを「スロー」し、最後にそれを処理する機能を備えています。おなじみtry/catch
のブロック。
この不自然な例を考えてみましょう。2つの関数があり、2番目の関数が最初の関数の結果を消費するとします。
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
ここで、firstFnとsecondFnの両方が完了するまでに長い時間がかかるため、このシーケンスを非同期で処理したいとします。まずdeferred
、一連の操作を表す新しいオブジェクトを作成します。
var deferred = $q.defer();
var promise = deferred.promise;
プロパティは、チェーンのpromise
最終的な結果を表します。作成直後にpromiseをログに記録すると、それが単なる空のオブジェクトであることがわかります({}
)。まだ見るものはありません。すぐに進んでください。
これまでのところ、私たちの約束はチェーンの出発点にすぎません。次に、2つの操作を追加しましょう。
promise = promise.then(firstFn).then(secondFn);
このthen
メソッドは、チェーンにステップを追加してから、拡張チェーンの最終的な結果を表す新しいpromiseを返します。ステップはいくつでも追加できます。
これまで、関数のチェーンを設定しましたが、実際には何も起こりません。deferred.resolve
チェーンの最初の実際のステップに渡す初期値を指定して、を呼び出すことから始めます。
deferred.resolve('initial value');
そして...それでも何も起こりません。モデルの変更が適切に観察されるようにするために、Angularは、次回$apply
が呼び出されるまで、チェーンの最初のステップを実際には呼び出しません。
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
では、エラー処理についてはどうでしょうか。これまでのところ、チェーンの各ステップで 成功ハンドラーを指定しただけです。then
オプションの2番目の引数としてエラーハンドラも受け入れます。これは、Promiseチェーンのもう1つの長い例で、今回はエラー処理を使用しています。
var firstFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'firstResult';
}
};
var secondFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'secondResult';
}
};
var thirdFn = function(param) {
// do something with param
return 'thirdResult';
};
var errorFn = function(message) {
// handle error
};
var deferred = $q.defer();
var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);
この例でわかるように、チェーン内の各ハンドラーには、トラフィックを次の成功ハンドラーではなく次のエラーハンドラーに転送する機会があります。ほとんどの場合、チェーンの最後に単一のエラーハンドラーを設定できますが、回復を試みる中間エラーハンドラーを設定することもできます。
あなたの例(そしてあなたの質問)にすぐに戻るために、私はそれらがFacebookのコールバック指向APIをAngularのモデル変更を観察する方法に適応させる2つの異なる方法を表していると言います。最初の例では、API呼び出しをpromiseでラップします。これはスコープに追加でき、Angularのテンプレートシステムによって理解されます。$scope.$digest()
2つ目は、コールバックの結果をスコープに直接設定し、Angularに外部ソースからの変更を認識させるため に呼び出す、より力ずくのアプローチを採用しています。
最初の例にはログイン手順がないため、2つの例を直接比較することはできません。ただし、一般的には、このような外部APIとのやり取りを個別のサービスにカプセル化し、その結果を約束どおりにコントローラーに配信することが望ましいです。そうすれば、コントローラーを外部の懸念から分離し、モックサービスを使用してより簡単にテストできます。