AngularJS:在哪里使用承诺? [英] AngularJS : Where to use promises?

查看:25
本文介绍了AngularJS:在哪里使用承诺?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I saw some examples of Facebook Login services that were using promises to access FB Graph API.

示例 #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()//手动范围评估" 的服务在得到响应时

And services that used "$scope.$digest() // Manual scope evaluation" when got the response

示例 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服务的原因案例是什么?
  • 它是如何工作的?
  • What is the difference in the examples above?
  • What are the reasons and cases to use $q service?
  • And how does it work?

推荐答案

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

This is not going to be a complete answer to your question, but hopefully this will help you and others when you try to read the documentation on the $q service. It took me a while to understand it.

让我们暂时将 AngularJS 放在一边,只考虑 Facebook API 调用.这两个 API 调用都使用回调机制来在来自 Facebook 的响应可用时通知调用者:

Let's set aside AngularJS for a moment and just consider the Facebook API calls. Both the API calls use a callback mechanism to notify the caller when the response from Facebook is available:

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

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

This is a standard pattern for handling asynchronous operations in JavaScript and other languages.

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

One big problem with this pattern arises when you need to perform a sequence of asynchronous operations, where each successive operation depends on the result of the previous operation. That's what this code is doing:

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

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

First it tries to log in, and then only after verifying that the login was successful does it make the request to the Graph API.

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

Even in this case, which is only chaining together two operations, things start to get messy. The method askFacebookForAuthentication accepts a callback for failure and success, but what happens when FB.login succeeds but FB.api fails? This method always invokes the success callback regardless of the result of the FB.api method.

现在想象一下,您正在尝试编写一个由三个或更多异步操作组成的稳健序列,其方式可以正确处理每一步的错误,并且在几周后对其他人甚至您都清晰易读.可能,但很容易一直嵌套这些回调并在此过程中忘记错误.

Now imagine that you're trying to code a robust sequence of three or more asynchronous operations, in a way that properly handles errors at each step and will be legible to anyone else or even to you after a few weeks. Possible, but it's very easy to just keep nesting those callbacks and lose track of errors along the way.

现在,让我们暂时将 Facebook API 放在一边,只考虑由 $q 服务实现的 Angular Promises API.此服务实现的模式是尝试将异步编程转回类似于线性系列简单语句的东西,能够在任何一步抛出"错误并在最后处理它,语义上类似于熟悉的 try/catch 块.

Now, let's set aside the Facebook API for a moment and just consider the Angular Promises API, as implemented by the $q service. The pattern implemented by this service is an attempt to turn asynchronous programming back into something resembling a linear series of simple statements, with the ability to 'throw' an error at any step of the way and handle it at the end, semantically similar to the familiar try/catch block.

考虑这个人为的例子.假设我们有两个函数,其中第二个函数使用第一个函数的结果:

Consider this contrived example. Say we have two functions, where the second function consumes the result of the first one:

 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 对象,它代表一个操作链:

Now imagine that firstFn and secondFn both take a long time to complete, so we want to process this sequence asynchronously. First we create a new deferred object, which represents a chain of operations:

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

promise 属性表示链的最终结果.如果您在创建承诺后立即记录它,您将看到它只是一个空对象 ({}).还没有什么可看的,继续前进.

The promise property represents the eventual result of the chain. If you log a promise immediately after creating it, you'll see that it is just an empty object ({}). Nothing to see yet, move right along.

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

So far our promise only represents the starting point in the chain. Now let's add our two operations:

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

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

The then method adds a step to the chain and then returns a new promise representing the eventual result of the extended chain. You can add as many steps as you like.

到目前为止,我们已经建立了我们的功能链,但实际上什么也没发生.您可以通过调用 deferred.resolve 开始工作,指定要传递给链中第一个实际步骤的初始值:

So far, we have set up our chain of functions, but nothing has actually happened. You get things started by calling deferred.resolve, specifying the initial value you want to pass to the first actual step in the chain:

 deferred.resolve('initial value');

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

And then...still nothing happens. To ensure that model changes are properly observed, Angular doesn't actually call the first step in the chain until the next time $apply is called:

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

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

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

So what about error handling? So far we have only specified a success handler at each step in the chain. then also accepts an error handler as an optional second argument. Here's another, longer example of a promise chain, this time with 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);

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

As you can see in this example, each handler in the chain has the opportunity to divert traffic to the next error handler instead of the next success handler. In most cases you can have a single error handler at the end of the chain, but you can also have intermediate error handlers that attempt recovery.

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

To quickly return to your examples (and your questions), I'll just say that they represent two different ways to adapt Facebook's callback-oriented API to 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's templating system. The second takes the more brute-force approach of setting the callback result directly on the scope, and then calling $scope.$digest() to make Angular aware of the change from an external source.

这两个例子没有直接可比性,因为第一个缺少登录步骤.但是,通常希望将与外部 API 的交互像这样封装在单独的服务中,并将结果作为承诺交付给控制器.这样,您就可以将控制器与外部关注点分开,并使用模拟服务更轻松地对其进行测试.

The two examples are not directly comparable, because the first is missing the login step. However, it's generally desirable to encapsulate interactions with external APIs like this in separate services, and deliver the results to controllers as promises. That way you can keep your controllers separate from external concerns, and test them more easily with mock services.

这篇关于AngularJS:在哪里使用承诺?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆