AngularJS:在哪里使用承诺?

Mak*_*sym 141 promise deferred angularjs

我看到了一些使用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;
}
Run Code Online (Sandbox Code Playgroud)

"$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
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

的jsfiddle

问题是:

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

kar*_*old 401

这不是对您的问题的完整答案,但希望当您尝试阅读有关该$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
  ...
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

现在,让我们暂时搁置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()); 
Run Code Online (Sandbox Code Playgroud)

现在假设firstFn和secondFn都需要很长时间才能完成,所以我们想要异步处理这个序列.首先,我们创建一个新deferred对象,它代表一系列操作:

 var deferred = $q.defer();
 var promise = deferred.promise;
Run Code Online (Sandbox Code Playgroud)

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

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

 promise = promise.then(firstFn).then(secondFn);
Run Code Online (Sandbox Code Playgroud)

then方法向链添加一个步骤,然后返回表示扩展链的最终结果的新promise.您可以根据需要添加任意数量的步骤.

到目前为止,我们已经建立了我们的功能链,但实际上并没有发生任何事情.通过调用启动事物deferred.resolve,指定要传递给链中第一个实际步骤的初始值:

 deferred.resolve('initial value');
Run Code Online (Sandbox Code Playgroud)

然后......仍然没有任何反应.为确保正确观察模型更改,Angular实际上不会调用链中的第一步,直到下次$apply调用:

 deferred.resolve('initial value');
 $rootScope.$apply();

 // or     
 $rootScope.$apply(function() {
    deferred.resolve('initial value');
 });
Run Code Online (Sandbox Code Playgroud)

那么错误处理呢?到目前为止,我们只在链中的每个步骤都指定了一个成功处理程序. 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);
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 我认为这是一个很好的答案!对我来说,最重要的是描述承诺真实的一般情况.老实说,我希望有另一个真实的例子(就像Facebook一样),但这也有效.非常感谢! (5认同)
  • 链接多个`then`方法的另一种方法是使用`$ q.all`.有关它的快速教程可以在[这里](https://egghead.io/lessons/angularjs-q-all)找到. (2认同)
  • 如果你需要等待多个*独立*异步操作来完成,`$ q.all`是合适的.如果每个操作都取决于前一个操作的结果,则它不会替换链接. (2认同)

Mar*_*son 9

我期待一个复杂的答案,它将涵盖两者:为什么它们一般被使用以及如何在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>
Run Code Online (Sandbox Code Playgroud)

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

  });
Run Code Online (Sandbox Code Playgroud)

(我知道它不能解决你的特定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.)