繁体   English   中英

AngularJS-承诺重新抛出捕获的异常

[英]AngularJS - Promises rethrow caught exceptions

在以下代码中,$ 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的异常?

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这样的更强大的库中,不需要这种东西。

附带说明-永远不会抛出字符串:那样会错过堆栈跟踪。

是虫子吗?

否。在源代码中查找$ q会发现,故意创建了一个try / catch块来响应由以下项引发的回调异常:

  1. 拒绝诺言,就像通过您所说的deferred.reject
  2. 调用已注册的Angular异常处理程序。 $ exceptionHandler文档中可以看到,此行为的默认行为是将其作为错误记录到浏览器控制台中,这就是您所观察到的。

...也被浏览器的错误处理所捕获

需要说明的是,异常不是由浏览器直接处理的,而是因为Angular调用console.error而显示为错误。

如何才能真正捕获$ q的异常?

当当前调用堆栈清除后,回调将在一段时间后执行,因此您将无法将外部函数包装在try / catch块中。 但是,您有2个选择:

  • 在回调中,在可能引发异常的代码周围放置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实现 您可以将其更改为完全不执行任何操作,因此控制台的错误日志中将永远没有任何内容,但是我认为我不建议这样做。

AngularJS 1.6版已修复

出现这种情况的原因是,未捕获的错误与常规拒绝不同,例如,它可能是由编程错误引起的。 实际上,这对于用户来说是令人困惑或不希望看到的,因为本机承诺和任何其他流行的承诺库都无法将抛出的错误与常规拒绝区分开。 (注意:尽管此行为不违反Promises / A +规范,但也没有规定。)

$ q:

由于e13eea ,从诺言的onFulfilledonRejection处理程序引发的错误与常规拒绝完全相同。 以前,它还会被传递给$exceptionHandler() (除了以错误为原因拒绝promise外)。

新行为适用于所有依赖$q服务/控制器/过滤器(包括内置服务,例如$http$route )。 例如, $http's transformRequest/Response函数或路由的redirectTo函数以及在路由的resolve对象中指定的函数如果抛出错误,将不再导致对$exceptionHandler()的调用。 除此之外,所有事物将继续以相同的方式运行。 即承诺将被拒绝,路由转换将被取消, $routeChangeError事件将被广播等。

-AngularJS开发人员指南-从V1.5迁移到V1.6-$ q

延迟是构造承诺的过时且非常糟糕的方法,使用构造函数可以解决此问题,甚至更多:

// 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
    });
}

这是一个示例测试,显示了新的$ 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');

}));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM