我有一个像这样的控制器:
function HomeController($location,$http) { activate(); function activate() { $http.get('non-existent-location') .then(function activateOk(response) { alert('Everything is ok'); }) .catch(function activateError(error) { alert('An error happened'); }); } }
像这样的HTTP拦截器:
function HttpInterceptor($q,$location) { var service = { responseError: responseError }; return service; function responseError(rejection) { if (rejection.status === 404) { $location.path('/error'); } return $q.reject(rejection); } }
这与浏览器重定向到’/ error’路径一样有效.但HomeController中的promise catch也在执行,我不希望这样.
我知道我可以对HomeController进行编码,使其忽略404错误,但这是不可维护的.假设我修改HttpInterceptor也处理500个错误,然后我必须再次修改HomeController(以及任何其他可能已添加的使用$http的控制器).有更优雅的解决方案吗?
HttpInterceptor中的一个小变化可用于中断/取消promise链,这意味着控制器上的activateOk或activateError都不会被执行.
function HttpInterceptor($q,$location) { var service = { responseError: responseError }; return service; function responseError(rejection) { if (rejection.status === 404) { $location.path('/error'); return $q(function () { return null; }) } return $q.reject(rejection); } }
该行返回$q(function(){return null;}),取消promise.
这是否“好”是一个争论的话题. Kyle Simpson在“You don’t know JS”中指出:
Many Promise abstraction libraries provide facilities to cancel
Promises,but this is a terrible idea! Many developers wish Promises
had natively been designed with external cancelation capability,but
the problem is that it would let one consumer/observer of a Promise
affect some other consumer’s ability to observe that same Promise.
This violates the future-value’s trustability (external immutability),
but morever is the embodiment of the “action at a distance”
anti-pattern…
好?坏?正如我所说,这是一个辩论的话题.我喜欢它不需要更改任何现有的$http消费者这一事实.
凯尔说得很对,他说:
Many Promise abstraction libraries provide facilities to cancel Promises…
例如,Bluebird promise库支持取消.从the documentation开始:
The new cancellation has “don’t care” semantics while the old
cancellation had abort semantics. Cancelling a promise simply means
that its handler callbacks will not be called.
选项2 – 不同的抽象
承诺是一个相对广泛的抽象.从Promises/A+ specification:
A promise represents the eventual result of an asynchronous operation.
Angular $http服务使用promise的$q实现来返回异步HTTP请求的最终结果的承诺.
值得一提的是$http有two deprecated functions,.success和.error,它们装饰了返回的promise.这些函数已被弃用,因为它们不能以promises的典型方式链接,并且被认为不会像“HTTP特定”函数集那样增加很多价值.
但这并不是说我们不能制作我们自己的HTTP抽象/包装器,它甚至不暴露$http使用的底层承诺.喜欢这个:
function HttpWrapper($http,$location) { var service = { get: function (getUrl,successCallback,errorCallback) { $http.get(getUrl).then(function (response) { successCallback(response); },function (rejection) { if (rejection.status === 404) { $location.path('/error'); } else { errorCallback(rejection); } }); } }; return service; }
由于这不会回报承诺,它的消费也需要有所不同:
HttpWrapper.get('non-existent-location',getSuccess,getError); function getSuccess(response) { alert('Everything is ok'); } function getError(error) { alert('An error happened'); }
在404的情况下,位置更改为“错误”,并且既不执行getSuccess也不执行getError回调.
此实现意味着链接HTTP请求的功能不再可用.这是可以接受的妥协吗?结果可能不同……
选项3 – 装饰拒绝
感谢TJ的评论:
if you need error handling in a particular controller,you will need
conditions to check if an error has been handled in
interceptor/service etc
HTTP拦截器可以使用处理的属性修饰promise拒绝,以指示它是否处理了错误.
function HttpInterceptor($q,$location) { var service = { responseError: responseError }; return service; function responseError(rejection) { if (rejection.status === 404) { $location.path('/error'); rejection.handled = true; } return $q.reject(rejection); } }
控制器然后看起来像这样:
$http.get('non-existent-location') .then(function activateOk(response) { alert('Everything is ok'); }) .catch(function activateError(error) { if (!error.handled) { alert('An error happened'); } });
概要
与选项2不同,选项3仍然为任何$http消费者留下链接承诺的选项,这在某种意义上是积极的,因为它没有消除功能.
选项2和3都具有较少的“远距离动作”.在选项2的情况下,替代抽象清楚地表明事物的行为将与通常的$q实现不同.对于选项3,消费者仍然会收到随心所欲的承诺.
所有3个选项都满足可维护性标准,因为更改全局错误处理程序以处理更多或更少的方案不需要更改使用者.