【问题标题】:AngularJS : Where to use promises?AngularJS:在哪里使用承诺?
【发布时间】:2013-03-14 07:23:27
【问题描述】:

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

示例 #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服务的理由案例有哪些?
  • 它是如何工作的?

【问题讨论】:

  • 听起来你应该阅读一下什么是承诺,以及为什么要普遍使用它们……它们不是 Angular 独有的,而且有很多可用的材料
  • @charlietfl,说得好,但我希望得到一个复杂的答案,涵盖两者:为什么它们被普遍使用以及如何在 Angular 中使用它。感谢您的建议

标签: angularjs deferred promise


【解决方案1】:

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

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

  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 = 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 也接受错误处理程序作为可选的第二个参数。这是另一个更长的 Promise 链示例,这次是错误处理:

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

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

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

这两个示例不能直接比较,因为第一个示例缺少登录步骤。但是,通常希望将与此类外部 API 的交互封装在单独的服务中,并将结果作为 Promise 传递给控制器​​。这样,您就可以将控制器与外部关注点分开,并使用模拟服务更轻松地对其进行测试。

【讨论】:

  • 我认为这是一个很好的答案!对我来说,主要的事情是描述当诺言真的是真实的一般情况。老实说,我希望有另一个真实的例子(就像 Facebook 一样),但我猜这也有效。非常感谢!
  • 链接多个then 方法的替代方法是使用$q.all。可以在here找到一个快速教程。
  • 如果您需要等待多个独立异步操作完成,$q.all 是合适的。如果每个操作都依赖于前一个操作的结果,它不会取代链接。
  • then 的链接在此简要说明。帮助我理解并充分利用它。谢谢
  • 很好的答案@karlgold!我有一个问题。如果在最后一个代码 sn-p 中将return 'firstResult' 部分更改为return $q.resolve('firstResult'),会有什么区别?
【解决方案2】:

我希望得到一个涵盖两者的复杂答案:为什么要使用它们 一般以及如何在 Angular 中使用它

这是 angular promises MVP (最小可行的 promise)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 示例,但我发现以下 sn-ps 很有用)

通过: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。)

【讨论】:

  • afaik 我们之所以如此热爱是因为我们很懒
  • 这帮助我理解了 $q、延迟和链式 .then 函数调用,非常感谢。
【解决方案3】:

延迟表示异步操作的结果。它公开了一个接口,该接口可用于指示状态和它所代表的操作结果。它还提供了一种获取相关 Promise 实例的方法。

Promise 提供了一个接口,用于与其相关的 deferred 进行交互,因此,相关方可以访问状态和 deferred 操作的结果。

创建 deferred 时,它的状态是 pending 并且没有任何结果。当我们 resolve() 或 reject() 延迟时,它会将其状态更改为已解决或已拒绝。尽管如此,我们仍然可以在创建延迟后立即获得相关的承诺,甚至可以分配与未来结果的交互。只有在延迟被拒绝或解决后,这些交互才会发生。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2018-10-02
      • 2012-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-10
      • 2017-05-18
      • 1970-01-01
      • 2018-07-27
      相关资源
      最近更新 更多