promisesを使用してFB Graph APIにアクセスしているFacebook Loginサービスの例をいくつか見ました。
例#1:
this.api = function(item) {
var deferred = $q.defer();
if (item) {
facebook.FB.api('/' + item, function (result) {
$rootScope.$apply(function () {
if (angular.isUndefined(result.error)) {
deferred.resolve(result);
} else {
deferred.reject(result.error);
}
});
});
}
return deferred.promise;
}
そして、応答を得たときに"$scope.$digest() // Manual scope evaluation"
を使用したサービス
例#2:
angular.module('HomePageModule', []).factory('facebookConnect', function() {
return new function() {
this.askFacebookForAuthentication = function(fail, success) {
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
}
}
});
function ConnectCtrl(facebookConnect, $scope, $resource) {
$scope.user = {}
$scope.error = null;
$scope.registerWithFacebook = function() {
facebookConnect.askFacebookForAuthentication(
function(reason) { // fail
$scope.error = reason;
}, function(user) { // success
$scope.user = user
$scope.$digest() // Manual scope evaluation
});
}
}
質問は次のとおりです。
これはあなたの質問に対する完全な答えになるわけではありませんが、$q
サービスのドキュメントを読むときに、あなたや他の人に役立つことを願っています。理解するのに少し時間がかかりました。
しばらくAngularJSを脇に置き、Facebook API呼び出しについて考えてみましょう。どちらのAPI呼び出しもcallbackメカニズムを使用して、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.');
}
});
最初にログインを試行し、ログインが成功したことを確認した後にのみGraph APIにリクエストを送信します。
この場合でも、2つの操作のみを連鎖しているため、事態は面倒になり始めます。メソッドaskFacebookForAuthentication
は失敗と成功のコールバックを受け入れますが、FB.login
は成功したがFB.api
が失敗するとどうなりますか?このメソッドは、FB.api
メソッドの結果に関係なく、常にsuccess
コールバックを呼び出します。
ここで、各ステップでエラーを適切に処理する方法で3つ以上の非同期操作の堅牢なシーケンスをコーディングしようとしていることを想像してください。可能ですが、それらのコールバックをネストし続けるだけで、途中でエラーを追跡するのは非常に簡単です。
それでは、Facebook APIを少しの間置いておき、$q
サービスによって実装されるAngular Promises APIを考えてみましょう。このサービスによって実装されるパターンは、非同期プログラミングを、一連の単純なステートメントに似たものに戻そうとする試みです。おなじみの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
プロパティは、チェーンの最終結果を表します。作成後すぐにプロミスをログに記録すると、それは単なる空のオブジェクト({}
)であることがわかります。まだ何も表示されていません。
これまでのところ、私たちの約束はチェーンの出発点に過ぎません。次に、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番目の引数としてエラーハンドラーも受け入れます。約束の連鎖のもう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);
この例でわかるように、チェーン内の各ハンドラーは、次のsuccessハンドラーではなく、次のerrorハンドラーにトラフィックを迂回させる機会があります。ほとんどの場合、チェーンの最後に単一のエラーハンドラを配置できますが、回復を試みる中間エラーハンドラを配置することもできます。
サンプル(および質問)にすばやく戻るために、Facebookのコールバック指向のAPIをモデルの変更を監視するAngularの方法に適合させる2つの異なる方法を示しているだけです。最初の例では、API呼び出しをプロミスでラップします。これはスコープに追加でき、Angularのテンプレートシステムによって理解されます。 2番目の方法は、スコープにコールバック結果を直接設定し、$scope.$digest()
を呼び出してAngularに外部ソースからの変更を認識させるという、より強引なアプローチを取ります。
最初の例ではログイン手順が欠落しているため、2つの例は直接比較できません。ただし、このような外部APIとの相互作用を別のサービスにカプセル化し、約束どおりに結果をコントローラーに配信することが一般的に望ましいです。これにより、コントローラーを外部の懸念事項から切り離し、モックサービスを使用してより簡単にテストできます。
私は両方をカバーする複雑な答えを期待していました:それらが一般的に使用される理由とAngularでの使用方法
これは角プロミスMVP(最小実行可能プロミス): http: //plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview
(リンクをクリックするのが面倒な人のために)
index.html
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="myModule" ng-controller="HelloCtrl">
<h1>Messages</h1>
<ul>
<li ng-repeat="message in messages">{{ message }}</li>
</ul>
</body>
</html>
app.js
angular.module('myModule', [])
.factory('HelloWorld', function($q, $timeout) {
var getMessages = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(['Hello', 'world']);
}, 2000);
return deferred.promise;
};
return {
getMessages: getMessages
};
})
.controller('HelloCtrl', function($scope, HelloWorld) {
$scope.messages = HelloWorld.getMessages();
});
(特定のFacebookの例を解決できないことはわかっていますが、以下のスニペットは便利です)
経由: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
2014年2月28日更新:1.2.0の時点で、約束はテンプレートによって解決されなくなりました。- http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html
(plunkerの例では1.1.5を使用します。)
遅延オブジェクトは、非同期操作の結果を表します。状態と、それが表す操作の結果を通知するために使用できるインターフェイスを公開します。また、関連するpromiseインスタンスを取得する方法も提供します。
Promiseは、関連する遅延オブジェクトとやり取りするためのインターフェースを提供するため、関係者が状態と遅延オペレーションの結果にアクセスできるようにします。
遅延を作成するとき、その状態は保留中であり、結果はありません。遅延オブジェクトをresolve()またはreject()すると、状態が解決または拒否に変わります。それでも、遅延を作成した直後に関連付けられた約束を取得し、将来の結果との相互作用を割り当てることもできます。これらの相互作用は、遅延が拒否または解決された後にのみ発生します。
コントローラ内でpromiseを使用し、データが利用可能かどうかを確認します
var app = angular.module("app",[]);
app.controller("test",function($scope,$q){
var deferred = $q.defer();
deferred.resolve("Hi");
deferred.promise.then(function(data){
console.log(data);
})
});
angular.bootstrap(document,["app"]);
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
</head>
<body>
<h1>Hello Angular</h1>
<div ng-controller="test">
</div>
</body>
</html>