在以下代码中,$ 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;
}
但是,当我查看控制台日志输出时,我看到以下内容:
异常是在Angular中捕获的,但也被浏览器的错误处理所捕获。使用Q库可以重现此行为。
这是一个错误吗?我怎样才能真正用$ q捕获异常?
答案 0 :(得分:16)
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;
}
这是为了区分拒绝与SyntaxError之类的错误。就个人而言,这是一个我不同意的设计选择,但它是可以理解的,因为$q
很小,所以你无法构建一个可靠的未处理的拒绝检测机制。在像Bluebird这样强大的图书馆中,不需要这种东西。
作为旁注 - 永远不要抛出字符串:你会错过堆栈跟踪。
答案 1 :(得分:6)
这是一个错误吗?
没有。查看source for $q表明创建了一个故意的try / catch块来响应
中回调的异常。deferred.reject
...也被浏览器的错误处理
抓住了
为了澄清,该异常不是由浏览器直接处理,而是因为Angular已调用console.error
如何使用$ q真正捕获异常?
回调在一段时间后执行,当前调用堆栈已清除,因此您无法在try
/ catch
块中包装外部函数。但是,您有两个选择:
在回调中围绕可能引发异常的代码放入try
/ catch
块:
f1().then(function(data) {
try {
return f2();
} catch(e) {
// Might want convert exception to rejected promise
return $q.reject(e);
}
})
更改Angular $exceptionHandler
服务的行为方式,例如How to override $exceptionHandler implementation。您可以将其更改为绝对没有任何内容,因此控制台的错误日志中永远不会有任何内容,但我不认为我会推荐它。
答案 2 :(得分:5)
这种行为的原因是未被捕获的错误与常规拒绝不同,因为 例如,它可能是由编程错误引起的。实际上,这结果令人困惑 或者对用户不利,因为既不是本地承诺也不是任何其他受欢迎的承诺库 将抛出的错误与常规拒绝区分开来。 (注意:虽然这种行为不符合Promises / A +规范,但也没有规定。)
$ Q:
由于e13eea,承诺的
onFulfilled
或onRejection
处理程序抛出的错误与常规拒绝完全相同。以前,它也会被传递给$exceptionHandler()
(除了以错误为理由拒绝承诺)。新行为适用于依赖
$q
的所有服务/控制器/过滤器等(包括内置服务,例如$http
和$route
)。例如,$http's transformRequest/Response
函数或路由的redirectTo函数以及路由的解析对象中指定的函数,如果它们抛出$exceptionHandler()
,将不再调用$routeChangeError
错误。除此之外,一切都会继续以同样的方式行事;即承诺将被拒绝,路线转换将被取消,{{1}}事件将被广播等。-- AngularJS Developer Guide - Migrating from V1.5 to V1.6 - $q
答案 3 :(得分:4)
延迟是一种过时的构造承诺的非常可怕的方法,使用构造函数解决了这个问题以及更多:
// 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
});
}
我不知道有角度的承诺是否支持上述内容,如果没有,你可以这样做:
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;
}
用法与promise构造函数相同:
function f1() {
return createPromise(function(resolve, reject){
// code
});
}
答案 4 :(得分:0)
这是一个示例测试,显示新的$ 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');
}));