在 AngularJS 中使用带有 Promises 的 success/error/finally/catch
Posted
技术标签:
【中文标题】在 AngularJS 中使用带有 Promises 的 success/error/finally/catch【英文标题】:Using success/error/finally/catch with Promises in AngularJS 【发布时间】:2014-06-26 21:10:47 【问题描述】:我在 AngularJs 中使用$http
,但我不确定如何使用返回的承诺和处理错误。
我有这个代码:
$http
.get(url)
.success(function(data)
// Handle data
)
.error(function(data, status)
// Handle HTTP error
)
.finally(function()
// Execute logic independent of success/error
)
.catch(function(error)
// Catch and handle exceptions from success/error/finally functions
);
这是一个好方法,还是有更简单的方法?
【问题讨论】:
【参考方案1】:您在寻找哪种类型的粒度?您通常可以通过:
$http.get(url).then(
//success function
function(results)
//do something w/results.data
,
//error function
function(err)
//handle error
);
我发现在链接多个 Promise 时,“finally”和“catch”会更好。
【讨论】:
在您的示例中,错误处理程序仅处理 $http 错误。 是的,我仍然需要处理成功/错误函数中的异常。然后我需要某种通用处理程序(我可以在其中设置loading = false
之类的东西)
你有一个大括号而不是括号来关闭你的 then() 调用。
这不适用于 404 响应错误,仅适用于 .catch()
方法
这是处理返回给控制器的http错误的正确答案【参考方案2】:
Promises 是对语句的抽象,它允许我们使用异步代码同步表达自己。它们代表一次性任务的执行。
它们还提供异常处理,就像普通代码一样,您可以从 Promise 中返回,也可以抛出。
你想要的同步代码是:
try
try
var res = $http.getSync("url");
res = someProcessingOf(res);
catch (e)
console.log("Got an error!",e);
throw e; // rethrow to not marked as handled
// do more stuff with res
catch (e)
// handle errors in processing or in error.
promisified 版本非常相似:
$http.get("url").
then(someProcessingOf).
catch(function(e)
console.log("got an error in initial processing",e);
throw e; // rethrow to not marked as handled,
// in $q it's better to `return $q.reject(e)` here
).then(function(res)
// do more stuff
).catch(function(e)
// handle errors in processing or in error.
);
【讨论】:
您将如何使用success()
、error()
和finally()
与catch()
结合使用?还是我必须使用then(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
@Joel 通常,您不想使用success
和error
(更喜欢.then
和.catch
,您可以(并且应该)省略errorFunction
从.then
使用 accatch
就像我上面的代码一样。
@BenjaminGruenbaum 您能否详细说明为什么您建议避免使用success
/error
?我的 Eclipse 在看到.catch(
时也会运行异常,所以我现在使用["catch"](
。如何驯服 Eclipse?
Angular 的 $q 库的 $http 模块实现使用 .success 和 .error 而不是 .then 和 .catch。然而,在我的测试中,当使用 .then 和 .catch 承诺时,我可以访问 $http 承诺的所有属性。另请参阅 zd333 的回答。
@SirBenBenji $q 没有 .success
和 .error
,$http 返回一个 $q 承诺加上 success
和 error
处理程序 - 但是,这些处理程序不会链接,如果/可能的话,通常应避免使用。一般来说 - 如果您有问题,最好将它们作为一个新问题提出,而不是作为对旧问题的评论。【参考方案3】:
在 Angular $http 的情况下,success() 和 error() 函数将解包响应对象,因此回调签名将类似于 $http(...).success(function(data, status, headers,配置))
对于 then(),您可能会处理原始响应对象。 比如贴在AngularJS $http API 文档中
$http(
url: $scope.url,
method: $scope.method,
cache: $templateCache
)
.success(function(data, status)
$scope.status = status;
$scope.data = data;
)
.error(function(data, status)
$scope.data = data || 'Request failed';
$scope.status = status;
);
最后的 .catch(...) 不需要,除非在之前的 Promise 链中抛出了新的错误。
【讨论】:
不推荐使用成功/错误方法。【参考方案4】:我认为前面的答案是正确的,但这是另一个例子(只是一个 f.y.i,success() 和 error() 根据 AngularJS Main page 已弃用:
$http
.get('http://someendpoint/maybe/returns/JSON')
.then(function(response)
return response.data;
).catch(function(e)
console.log('Error: ', e);
throw e;
).finally(function()
console.log('This finally block');
);
【讨论】:
据我所知,最终没有返回响应。【参考方案5】:忘记使用success
和error
方法吧。
这两种方法在 Angular 1.4 中都已弃用。基本上,弃用背后的原因是它们不是 chainable-friendly,可以这么说。
通过以下示例,我将尝试说明success
和error
不链式友好 的含义。假设我们调用一个 API,返回一个带有地址的用户对象:
用户对象:
name: 'Igor', address: 'San Francisco'
调用 API:
$http.get('/user')
.success(function (user)
return user.address; <---
) | // you might expect that 'obj' is equal to the
.then(function (obj) ------ // address of the user, but it is NOT
console.log(obj); // -> name: 'Igor', address: 'San Francisco'
);
;
发生了什么?
因为success
和error
返回的是原来的promise,即$http.get
返回的那个,所以对象传递给then
的回调是整个 user 对象,也就是说与前面的success
回调相同的输入。
如果我们将两个then
链接起来,就不会那么混乱了:
$http.get('/user')
.then(function (user)
return user.address;
)
.then(function (obj)
console.log(obj); // -> 'San Francisco'
);
;
【讨论】:
另外值得注意的是success
和error
是$http
调用的only added to the immediate return(不是原型),所以如果你在它们之间调用另一个promise方法(比如,你通常调用return $http.get(url)
包装在基础库中,但后来决定使用 return $http.get(url).finally(...)
切换库调用中的微调器),那么您将不再拥有那些方便的方法。【参考方案6】:
我这样做就像 Bradley Braithwaite 在他的 blog 中建议的那样:
app
.factory('searchService', ['$q', '$http', function($q, $http)
var service = ;
service.search = function search(query)
// We make use of Angular's $q library to create the deferred instance
var deferred = $q.defer();
$http
.get('http://localhost/v1?=q' + query)
.success(function(data)
// The promise is resolved once the HTTP call is successful.
deferred.resolve(data);
)
.error(function(reason)
// The promise is rejected if there is an error with the HTTP call.
deferred.reject(reason);
);
// The promise is returned to the caller
return deferred.promise;
;
return service;
])
.controller('SearchController', ['$scope', 'searchService', function($scope, searchService)
// The search service returns a promise API
searchService
.search($scope.query)
.then(function(data)
// This is set when the promise is resolved.
$scope.results = data;
)
.catch(function(reason)
// This is set in the event of an error.
$scope.error = 'There has been an error: ' + reason;
);
])
关键点:
resolve 函数链接到控制器中的 .then 函数,即一切正常,因此我们可以信守承诺并解决它。
reject 函数链接到我们控制器中的 .catch 函数,即出现问题,因此我们无法兑现承诺,需要 拒绝它。
它非常稳定和安全,如果您有其他条件拒绝promise,您可以随时在成功函数中过滤您的数据并致电deferred.reject(anotherReason)
并说明拒绝的原因。
正如 Ryan Vice 在 cmets 中建议的那样,这可能不会被视为有用,除非您对响应稍作调整,可以这么说。
因为 success
和 error
自 1.4 起已弃用,也许最好使用常规的 promise 方法 then
和 catch
并在这些方法中转换响应并返回转换后响应的 promise。
我展示了两种方法和第三种中间方法的相同示例:
success
和 error
方法(success
和 error
返回一个 HTTP 响应的承诺,所以我们需要 $q
的帮助来返回一个数据的承诺):
function search(query)
// We make use of Angular's $q library to create the deferred instance
var deferred = $q.defer();
$http.get('http://localhost/v1?=q' + query)
.success(function(data,status)
// The promise is resolved once the HTTP call is successful.
deferred.resolve(data);
)
.error(function(reason,status)
// The promise is rejected if there is an error with the HTTP call.
if(reason.error)
deferred.reject(text:reason.error, status:status);
else
//if we don't get any answers the proxy/api will probably be down
deferred.reject(text:'whatever', status:500);
);
// The promise is returned to the caller
return deferred.promise;
;
then
和 catch
方法(这有点难以测试,因为抛出):
function search(query)
var promise=$http.get('http://localhost/v1?=q' + query)
.then(function (response)
// The promise is resolved once the HTTP call is successful.
return response.data;
,function(reason)
// The promise is rejected if there is an error with the HTTP call.
if(reason.statusText)
throw reason;
else
//if we don't get any answers the proxy/api will probably be down
throw statusText:'Call error', status:500;
);
return promise;
虽然有一个中途的解决方案(这样您可以避免 throw
并且无论如何您可能需要使用 $q
来模拟测试中的承诺行为):
function search(query)
// We make use of Angular's $q library to create the deferred instance
var deferred = $q.defer();
$http.get('http://localhost/v1?=q' + query)
.then(function (response)
// The promise is resolved once the HTTP call is successful.
deferred.resolve(response.data);
,function(reason)
// The promise is rejected if there is an error with the HTTP call.
if(reason.statusText)
deferred.reject(reason);
else
//if we don't get any answers the proxy/api will probably be down
deferred.reject(statusText:'Call error', status:500);
);
// The promise is returned to the caller
return deferred.promise;
欢迎任何形式的 cmets 或更正。
【讨论】:
为什么要使用 $q 将 promise 包装在 promise 中。为什么不直接返回 $http.get() 返回的 promise? 因为success()
和error()
不会像then()
那样返回新的承诺。使用$q
,我们让工厂返回数据的承诺,而不是 HTTP 响应的承诺。
你的回答让我很困惑,所以也许我没有很好地解释自己。除非您正在操纵响应,否则您可以简单地返回 $http 返回的承诺。看看我刚刚写的这个例子:jsbin.com/belagan/edit?html,js,output
我看不到价值。我觉得没有必要,我拒绝对使用这种方法的项目进行代码审查,但如果你从中获得价值,那么你应该使用它。我还在 Angular 最佳实践文章中看到了一些承诺,称不必要的包装是一种气味。
这是deferred anti-pattern。阅读You're Missing the Point of Promises以上是关于在 AngularJS 中使用带有 Promises 的 success/error/finally/catch的主要内容,如果未能解决你的问题,请参考以下文章
如何对 AngularJS $promise.then 进行单元测试
Promise.catch() 不会在 AngularJS 单元测试中捕获异常