AngularJS - 承诺重新抛出异常

时间:2014-04-27 15:01:07

标签: javascript angularjs promise angular-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;
}  

但是,当我查看控制台日志输出时,我看到以下内容:

enter image description here

异常是在Angular中捕获的,但也被浏览器的错误处理所捕获。使用Q库可以重现此行为。

这是一个错误吗?我怎样才能真正用$ q捕获异常?

5 个答案:

答案 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块来响应

中回调的异常。
  1. 拒绝承诺,就像通过你致电deferred.reject
  2. 一样
  3. 调用已注册的Angular例外处理程序。正如在$exceptionHandler docs中可以看到的,默认行为是将其作为错误记录到浏览器控制台,这是您所观察到的。
  4.   

    ...也被浏览器的错误处理

    抓住了

    为了澄清,该异常不是由浏览器直接处理,而是因为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)

使用AngularJS版本1.6

进行了修复

这种行为的原因是未被​​捕获的错误与常规拒绝不同,因为 例如,它可能是由编程错误引起的。实际上,这结果令人困惑 或者对用户不利,因为既不是本地承诺也不是任何其他受欢迎的承诺库 将抛出的错误与常规拒绝区分开来。 (注意:虽然这种行为不符合Promises / A +规范,但也没有规定。)

  

$ Q:

     

由于e13eea,承诺的onFulfilledonRejection处理程序抛出的错误与常规拒绝完全相同。以前,它也会被传递给$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');

}));