AngularJS: 在哪里使用承诺?

我看到了一些使用 我保证访问 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服务的 原因案件是什么?
  • 它是如何 工作的?
100101 次浏览

这不会完全回答您的问题,但是希望在您尝试阅读有关 $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.');
}
});

首先,它尝试登录,然后只有在验证登录成功之后才向 GraphAPI 发出请求。

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

现在假设您正在尝试编写一个由三个或更多异步操作组成的健壮序列,其方式是在每个步骤中正确处理错误,并且在几周后对其他任何人甚至对您都是可读的。有可能,但是很容易一直嵌套这些回调函数,并在此过程中丢失错误的跟踪。

现在,让我们暂时把 Facebook 的 API 放在一边,只考虑一下由 $q服务实现的 Angular 誓言 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 和 Second Fn 都需要很长时间才能完成,因此我们希望异步处理这个序列。首先,我们创建一个新的 deferred对象,它表示一系列操作:

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

promise属性表示链的最终结果。如果您在创建承诺之后立即记录它,您将看到它只是一个空对象({})。没什么好看的,快走吧。

到目前为止,我们的承诺只是这个链条的起点,现在让我们加上两个操作:

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

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

到目前为止,我们已经建立了我们的函数链,但实际上什么也没有发生。您可以通过调用 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);

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

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

这两个示例没有直接的可比性,因为第一个示例缺少登录步骤。然而,通常需要在单独的服务中封装与外部 API 的交互,并将结果作为承诺交付给控制器。通过这种方式,您可以将控制器与外部关注点分开,并使用模拟服务更容易地测试它们。

我期待一个复杂的答案,将涵盖这两个问题: 为什么它们被用在 以及如何使用它的角度

这是 有棱有角的承诺最有价值球员 (最低可行承诺): http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview的重头戏

来源:

(对于那些懒得点击链接的人)

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>

应用程序

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/


2014年2月28日更新: 从1.2.0开始,承诺不再通过模板来解决。 Http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(柱塞示例使用1.1.5)

延迟表示异步操作的结果。它公开了一个接口,该接口可用于向状态及其所表示的操作的结果发送信号。它还提供了一种获取关联的承诺实例的方法。

一个承诺提供了一个接口,用于与其相关的延迟操作进行交互,因此,允许感兴趣的各方访问状态和延迟操作的结果。< br > < br > 当创建一个延迟时,它的状态是挂起的,没有任何结果。当我们解析()或拒绝()延迟时,它将其状态更改为解析或拒绝。尽管如此,我们仍然可以在创建一个延迟后立即获得相关的承诺,甚至可以分配与其未来结果的交互。这些交互只有在延迟拒绝或解决之后才会发生。

在控制器中使用承诺,并确保数据可用或不可用

 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>