AngularJS:在哪里使用诺言?

我看到一些使用Promise访问FB Graph API的Facebooklogin服务的例子。

示例#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 }); } } 

的jsfiddle

问题是:

  • 上面的例子有什么不同
  • 使用$ q服务的原因案例是什么?
  • 它是如何工作的

这不是你的问题的完整答案,但希望这可以帮助你和其他人,当你尝试阅读$q服务的文档。 我花了一段时间才明白这一点。

我们暂且搁置AngularJS,只考虑Facebook API调用。 当来自Facebook的响应可用时,这两个API调用都使用callback机制来通知调用者:

  facebook.FB.api('/' + item, function (result) { if (result.error) { // handle error } else { // handle success } }); // program continues while request is pending ... 

这是处理JavaScript和其他语言的asynchronous操作的标准模式。

当需要执行一系列asynchronous操作时,出现这种模式的一个大问题,即每个连续操作都依赖于前一个操作的结果。 这就是这个代码在做什么:

  FB.login(function(response) { if (response.authResponse) { FB.api('/me', success); } else { fail('User cancelled login or did not fully authorize.'); } }); 

首先尝试login,然后只有在validationlogin成功后,才向Graph API发出请求。

即使在这样的情况下,只是将两个操作链接在一起,事情开始变得混乱。 askFacebookForAuthentication方法接受一个失败和成功的callback,但是当FB.login成功但是FB.api失败时FB.api发生什么? 无论FB.api方法的结果如何,此方法总是调用successcallback。

现在想象一下,您正在试图编写一个健壮的三个或三个以上asynchronous操作序列,以正确的方式处理每个步骤中的错误,几个星期后,任何人甚至是您都可以看清楚。 可能的,但是保持嵌套这些callback并且一路追踪错误是很容易的。

现在,让我们暂时搁置Facebook API,并考虑$q服务实现的Angular Promises API。 这个服务实现的模式试图把asynchronous编程变成类似于一系列简单语句的线程,能够在任何一步“抛出”错误并在最后处理它,在语义上类似于熟悉的try/catch块。

考虑这个人为的例子。 假设我们有两个函数,第二个函数消耗第一个函数的结果:

  var firstFn = function(param) { // do something with param return 'firstResult'; }; var secondFn = function(param) { // do something with param return 'secondResult'; }; secondFn(firstFn()); 

现在设想firstFn和secondFn都需要很长时间才能完成,所以我们想要asynchronous处理这个序列。 首先我们创build一个新的deferred对象,代表一个操作链:

  var deferred = $q.defer(); var promise = deferred.promise; 

promise属性表示链的最终结果。 如果您在创build后立即logging承诺,则会看到它只是一个空对象( {} )。 没什么可看的,一直往前走。

到目前为止,我们的承诺只是代表链条的起点。 现在我们来添加我们的两个操作:

  promise = promise.then(firstFn).then(secondFn); 

then方法向链中添加一个步骤,然后返回代表扩展链最终结果的新promise。 您可以添加尽可能多的步骤,只要你喜欢。

到目前为止,我们已经build立了一系列的职能,但实际上并没有发生任何事情。 你通过调用deferred.resolve得到一些东西,指定你想传递给链中第一个实际步骤的初始值:

  deferred.resolve('initial value'); 

然后…仍然没有任何反应。 为了确保模型更改得到正确遵守,Angular并没有实际调用链中的第一步,直到下一次调用$apply

  deferred.resolve('initial value'); $rootScope.$apply(); // or $rootScope.$apply(function() { deferred.resolve('initial value'); }); 

那么error handling呢? 到目前为止,我们只在链中的每一步指定了一个成功处理程序then也接受一个error handling程序作为可选的第二个参数。 下面是另一个承诺链的更长的例子,这次是error handling:

  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); 

正如您在本例中看到的,链中的每个处理程序都有机会将stream量转移到下一个错误处理程序,而不是下一个成功处理程序。 在大多数情况下,您可以在链的末尾有一个error handling程序,但是也可以有尝试恢复的中间error handling程序。

为了快速返回到您的示例(和您的问题),我只是说他们代表了两种不同的方式来使Facebook的面向callback的API适应Angular观察模型更改的方式。 第一个示例将API调用包装在一个承诺中,该承诺可以添加到作用域中,并由Angular的模板系统来理解。 第二种方法是直接在范围上设置callback结果,然后调用$scope.$digest()使Angular知道外部源的改变。

这两个例子不能直接比较,因为第一个缺lesslogin步骤。 然而,通常需要在不同的服务中封装与外部API的交互,并将结果作为承诺传递给控制器​​。 这样,您可以使控制器与外部问题分离,并通过模拟服务更轻松地进行testing。

我期望一个复杂的答案,将涵盖两个方面:为什么他们一般使用,以及如何在Angular中使用它

这是angular色承诺MVP (最低可行的承诺) plunk: 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的例子,但我发现以下代码片段有用)

Via: 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)

推迟表示asynchronous操作的结果。 它公开了一个接口,可以用来表示状态和它所表示的操作的结果。 它还提供了一种获取关联的promise实例的方法。

承诺提供了一个接口,用于与相关的延迟进行交互,因此允许感兴趣的各方访问状态和延迟操作的结果。

当创build一个延期时,它的状态是挂起的,它没有任何结果。 当我们parsing()或拒绝()延迟,它改变它的状态解决或拒绝。 尽pipe如此,我们可以在创build延期后立即获得关联的承诺,甚至可以为未来的结果分配交互。 这些交互只会在延期被拒绝或解决之后才会发生。

在控制器中使用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>