AngularJS:在哪里使用promise?
我看到一些使用Promise访问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
服务中的文档时。 我花了一段时间才明白它。
让我们暂时搁置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.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
也接受一个错误处理程序作为可选的第二个参数。 这是一个承诺链的另一个更长的例子,这次有错误处理:
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的交互封装在单独的服务中,并将结果作为承诺提供给控制器。 这样,您可以使控制器与外部问题分离,并通过模拟服务更轻松地进行测试。
我期望得到一个复杂的答案,涵盖两个方面:它们为什么被广泛使用以及如何在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>
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例子,但我发现以下代码片段很有用)
Via: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)。
推迟表示异步操作的结果。 它公开了一个接口,可以用来表示状态以及它所表示的操作的结果。 它还提供了获取相关诺言实例的方法。
承诺提供了一个接口,用于与相关的延迟进行交互,因此允许感兴趣的各方访问状态和延迟操作的结果。
当创建延迟时,它的状态处于挂起状态,并且没有任何结果。 当我们解析()或拒绝()延迟时,它会将其状态更改为已解决或拒绝。 不过,我们可以在创建延期后立即获得相关承诺,甚至可以为其未来结果分配交互。 这些交互只有在延期被拒绝或解决后才会发生。