这不会是您问题的完整答案,但希望这将在您尝试阅读$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
失败时会发生什么?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());
现在想象 firstFn 和 secondFn 都需要很长时间才能完成,所以我们要异步处理这个序列。首先我们创建一个新deferred
对象,它代表一个操作链:
var deferred = $q.defer();
var promise = deferred.promise;
该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
还接受错误处理程序作为可选的第二个参数。这是另一个更长的 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 中,该 Promise 可以添加到一个范围内,并且可以被 Angular 的模板系统理解。第二种采用更暴力的方法,直接在作用域上设置回调结果,然后调用$scope.$digest()
以使 Angular 知道来自外部源的更改。
这两个示例不能直接比较,因为第一个示例缺少登录步骤。但是,通常希望将与此类外部 API 的交互封装在单独的服务中,并将结果作为 Promise 传递给控制器。这样,您可以将控制器与外部关注点分开,并使用模拟服务更轻松地测试它们。