首页 文章

AngularJS:在哪里使用承诺?

提问于
浏览
140

我看到了一些使用 promises 访问FB Graph API的Facebook登录服务示例 .

Example #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" 的服务

Example #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

问题是:

  • 上面例子中的 difference 是什么?

  • 使用$ q服务的 reasonscases 是什么?

  • 怎么回事 work

4 回答

  • 400

    这不是您问题的完整答案,但希望当您尝试阅读 $q 服务上的文档时,这将有助于您和其他人 . 我花了一段时间来理解它 .

    让我们暂时搁置AngularJS并考虑Facebook API调用 . 两个API调用都使用回调机制在Facebook的响应可用时通知调用者:

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

    这是用于处理JavaScript和其他语言中的异步操作的标准模式 .

    当您需要执行一系列异步操作时,会出现这种模式的一个大问题,其中每个连续操作都取决于前一个操作的结果 . 这就是这段代码的作用:

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

    首先它尝试登录,然后只有在验证登录成功后才向Graph API发出请求 .

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

    现在想象一下,您正在尝试编写一个包含三个或更多异步操作的强大序列,以便在每个步骤中正确处理错误,并且在几周后对任何其他人甚至是您都是清晰的 . 可能,但是很容易就是一直保持嵌套这些回调并且在此过程中失去对错误的跟踪 .

    现在,让我们暂时搁置Facebook API,并考虑由 $q 服务实现的Angular Promises API . 该服务实现的模式是尝试将异步编程转换为类似线性系列简单语句的东西,能够在任何一步发生错误并在最后处理它,在语义上类似于熟悉的 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都需要很长时间才能完成,所以我们想要异步处理这个序列 . 首先,我们创建一个新的 deferred 对象,它代表一系列操作:

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

    promise 属性表示链的最终结果 . 如果您在创建承诺后立即记录承诺,您将看到它只是一个空对象( {} ) . 什么都看不到,向前走 .

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

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

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

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

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

    为了快速回到你的例子(以及你的问题),我向Angular 's way of observing model changes. The first example wraps the API call in a promise, which can be added to a scope and is understood by Angular'的模板系统提供了面向回调的API . 第二种方法采用更强力的方法直接在作用域上设置回调结果,然后调用 $scope.$digest() 使Angular知道来自外部源的更改 .

    这两个示例不能直接比较,因为第一个示例缺少登录步骤 . 但是,通常需要在单独的服务中封装与此类外部API的交互,并将结果作为承诺提供给控制器 . 这样,您可以将控制器与外部问题分开,并使用模拟服务更轻松地测试它们 .

  • 1

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

    这是 angular promises 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/


    Update 28th Feb 2014: 从1.2.0开始,模板不再解析承诺 . http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

    (plunker示例使用1.1.5 . )

  • 1

    延迟表示异步操作的结果 . 它公开了一个接口,可用于发信号通知状态及其所代表的操作结果 . 它还提供了获取关联的promise实例的方法 .

    promise提供了与其相关的延迟交互的接口,因此,允许感兴趣的各方访问状态和延迟操作的结果 .

    创建延迟时,它的状态处于暂挂状态,并且没有任何结果 . 当我们解决()或拒绝()延迟时,它会将其状态更改为已解决或拒绝 . 尽管如此,我们可以在创建延迟后立即获得相关承诺,甚至可以分配与其未来结果的交互 . 这些交互只会在延期拒绝或解决之后发生 .

  • 9

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

相关问题