javascript AngularJS - 承诺重新抛出捕获的异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23324942/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
AngularJS - Promises rethrow caught exceptions
提问by VitalyB
In the following code, an exception is caught by the catchfunction of the $q promise:
在下面的代码中,$q promise的catch函数捕获了一个异常:
// Fiddle - http://jsfiddle.net/EFpn8/6/
f1().then(function(data) {
console.log("success 1: "+data)
return f2();
})
.then(function(data) {console.log("success 2: "+data)})
.catch(function(data) {console.log("error: "+data)});
function f1() {
var deferred = $q.defer();
// An exception thrown here is not caught in catch
// throw "err";
deferred.resolve("done f1");
return deferred.promise;
}
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
However when I look in the console log output I see the following:
但是,当我查看控制台日志输出时,我看到以下内容:
The exception was caught in Angular, but was also caught by the error handling of the browser. This behavior does reproduce with Q library.
异常在Angular中被捕获,但也被浏览器的错误处理捕获。这种行为确实会在 Q 库中重现。
Is it a bug? How can I truly catch an exception with $q?
这是一个错误吗?我怎样才能真正用 $q 捕获异常?
采纳答案by georgeawg
Fixed with AngularJS version 1.6
已在 AngularJS 1.6 版中修复
The reasoning for this behavior was that an uncaught error is different than a regular rejection, as it can be caused by a programming error, for example. In practice, this turned out to be confusing or undesirable for users, since neither native promises nor any other popular promise library distinguishes thrown errors from regular rejections. (Note: While this behavior does not go against the Promises/A+ spec, it is not prescribed either.)
这种行为的原因是未捕获的错误与常规拒绝不同,例如,它可能是由编程错误引起的。在实践中,这对用户来说是令人困惑或不受欢迎的,因为原生 Promise 和任何其他流行的 Promise 库都没有将抛出的错误与常规拒绝区分开来。(注意:虽然这种行为不违反 Promises/A+ 规范,但也没有规定。)
$q:
Due to e13eea, an error thrown from a promise's
onFulfilled
oronRejection
handlers is treated exactly the same as a regular rejection. Previously, it would also be passed to the$exceptionHandler()
(in addition to rejecting the promise with the error as reason).The new behavior applies to all services/controllers/filters etc that rely on
$q
(including built-in services, such as$http
and$route
). For example,$http's transformRequest/Response
functions or a route's redirectTo function as well as functions specified in a route's resolve object, will no longer result in a call to$exceptionHandler()
if they throw an error. Other than that, everything will continue to behave in the same way; i.e. the promises will be rejected, route transition will be cancelled,$routeChangeError
events will be broadcasted etc.-- AngularJS Developer Guide - Migrating from V1.5 to V1.6 - $q
$q:
由于e13eea,从承诺
onFulfilled
或onRejection
处理程序抛出的错误被视为与常规拒绝完全相同。以前,它也会被传递给$exceptionHandler()
(除了以错误为理由拒绝承诺之外)。新行为适用于所有依赖的服务/控制器/过滤器等
$q
(包括内置服务,例如$http
和$route
)。例如,$http's transformRequest/Response
函数或路由的 redirectTo 函数以及路由的解析对象中指定的函数,$exceptionHandler()
如果它们抛出错误,将不再导致调用。除此之外,一切都将继续以同样的方式运行;即承诺将被拒绝,路由转换将被取消,$routeChangeError
事件将被广播等。
回答by Benjamin Gruenbaum
Angular's $q
uses a convention where thrown errors are logged regardlessof being caught. Instead, if you want to signal a rejection you need to return $q.reject(...
as such:
Angular$q
使用一种约定,无论是否被捕获,都会记录抛出的错误。相反,如果您想表示拒绝,您需return $q.reject(...
要这样做:
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
return $q.reject(new Error("err"));//throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
This is to distinguish rejections from errors like SyntaxError. Personally, it's a design choice I disagree with but it's understandable since $q
is tiny so you can't really build in a reliable unhandled rejection detection mechanism. In stronger libraries like Bluebird, this sort of thing is not required.
这是为了将拒绝与 SyntaxError 之类的错误区分开来。就我个人而言,这是一个我不同意的设计选择,但这$q
是可以理解的,因为它很小,因此您无法真正构建可靠的未处理拒绝检测机制。在像 Bluebird 这样更强大的库中,这种事情不是必需的。
As a side note - never, ever throw strings : you miss on stack traces that way.
作为旁注 - 永远不要抛出字符串:您会以这种方式错过堆栈跟踪。
回答by Michal Charemza
Is it a bug?
这是一个错误吗?
No. Looking in the source for $qreveals that a deliberate try / catch block is created to respond to exceptions thrown in the callback by
不。查看 $q的源代码表明,故意创建了一个 try/catch 块来响应回调中抛出的异常
- Rejecting the promise, as through you had called
deferred.reject
- Calling the registered Angular exception hander. As can be seen in the $exceptionHandler docs, the default behaviour of this is to log it to the browser console as an error, which is what you have observed.
- 拒绝承诺,就像通过你打电话
deferred.reject
- 调用注册的 Angular 异常处理程序。正如在$exceptionHandler 文档中所见,默认行为是将其作为错误记录到浏览器控制台,这就是您所观察到的。
... was also caught by the error handling of the browser
...也被浏览器的错误处理所捕获
To clarify, the exception isn't handled directly by the browser, but appears as an error because Angular has called console.error
澄清一下,异常不是由浏览器直接处理,而是显示为错误,因为 Angular 调用了 console.error
How can I truly catch an exception with $q?
我怎样才能真正用 $q 捕获异常?
The callbacks are executed some time later, when the current call stack has cleared, so you won't be able to wrap the outer function in try
/ catch
block. However, you have 2 options:
回调会在一段时间后执行,当当前调用堆栈已清除时,您将无法将外部函数包装在try
/catch
块中。但是,您有两个选择:
Put in
try
/catch
block around the code that might throw the exception, within the callback:f1().then(function(data) { try { return f2(); } catch(e) { // Might want convert exception to rejected promise return $q.reject(e); } })
Change how Angular's
$exceptionHandler
service behaves, like at How to override $exceptionHandler implementation. You could potentially change it to do absolutely nothing, so there would never be anything in the console's error log, but I don't think I would recommend that.
在回调中,在可能抛出异常的代码周围放入
try
/catch
块:f1().then(function(data) { try { return f2(); } catch(e) { // Might want convert exception to rejected promise return $q.reject(e); } })
更改 Angular
$exceptionHandler
服务的行为方式,例如如何覆盖 $exceptionHandler implementation。您可以将其更改为绝对不执行任何操作,因此控制台的错误日志中永远不会有任何内容,但我认为我不建议这样做。
回答by Esailija
The deferred is an outdated and a really terrible way of constructing promises, using the constructor solves this problem and more:
deferred 是一种过时且非常糟糕的构建 Promise 的方式,使用构造函数解决了这个问题,还有更多:
// This function is guaranteed to fulfill the promise contract
// of never throwing a synchronous exception, using deferreds manually
// this is virtually impossible to get right
function f1() {
return new Promise(function(resolve, reject) {
// code
});
}
I don't know if angular promises support the above, if not, you can do this:
不知道angular promises是否支持以上,如果不支持,你可以这样做:
function createPromise(fn) {
var d = $q.defer();
try {
fn(d.resolve.bind(d), d.reject.bind(d));
}
catch (e) {
d.reject(e);
}
return d.promise;
}
Usage is same as promise constructor:
用法与 promise 构造函数相同:
function f1() {
return createPromise(function(resolve, reject){
// code
});
}
回答by user2747330
Here is an sample test that shows the new $q construction function, use of .finally(), rejections, and promise chain propagations:
这是一个示例测试,显示了新的 $q 构造函数、.finally() 的使用、拒绝和承诺链传播:
iit('test',inject(function($q, $timeout){
var finallyCalled = false;
var failValue;
var promise1 = $q.when(true)
.then(function(){
return $q(function(resolve,reject){
// Reject promise1
reject("failed");
});
})
.finally(function(){
// Always called...
finallyCalled = true;
// This will be ignored
return $q.when('passed');
});
var promise2 = $q.when(promise1)
.catch(function(value){
// Catch reject of promise1
failValue = value;
// Continue propagation as resolved
return value+1;
// Or continue propagation as rejected
//return $q.reject(value+2);
});
var updateFailValue = function(val){ failValue = val; };
$q.when(promise2)
.then( updateFailValue )
.catch(updateFailValue );
$timeout.flush();
expect( finallyCalled ).toBe(true);
expect( failValue ).toBe('failed1');
}));