示例#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()//手动范围评估”的服务
示例#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 }); } }
问题是:
>上面的例子有什么区别?
>使用$ 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方法的结果如何。
现在想象你试图编写一个健壮的三个或更多异步操作序列,在每个步骤正确处理错误的方式,任何人或甚至你的几个星期后可以清晰。可能,但是很容易只是保持嵌套这些回调,并在一路上丢失跟踪错误。
现在,让我们暂时搁置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'); });
那么关于错误处理呢?到目前为止,我们只在链中的每个步骤指定了一个成功处理程序。然后还接受一个错误处理程序作为可选的第二个参数。这里是另一个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);
从这个例子中可以看出,链中的每个处理程序都有机会将流量转移到下一个错误处理程序,而不是下一个成功处理程序。在大多数情况下,您可以在链接的末尾有一个错误处理程序,但您也可以有尝试恢复的中间错误处理程序。
为了快速回到你的例子(和你的问题),我只是说,他们代表两种不同的方式来适应Facebook的回调导向的API,以Angular的方式观察模型的变化。第一个例子将API调用包装在一个promise中,它可以被添加到一个作用域,并被Angular的模板系统理解。第二种方法采用更强大的方法,直接在范围上设置回调结果,然后调用$ scope。$ digest()使Angular知道来自外部源的更改。
这两个示例不能直接比较,因为第一个缺少登录步骤。然而,通常希望将与这样的外部API的交互封装在单独的服务中,并将结果作为promise传递给控制器。这样,您可以保持您的控制器与外部关注分离,并使用模拟服务更容易地测试它们。