Javascript AngularJS-承诺重新捕获异常

Javascript AngularJS-承诺重新捕获异常,javascript,angularjs,promise,angular-promise,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(dat

在以下代码中,$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
使用一种约定,不管捕获到什么,都会记录抛出的错误。相反,如果要发出拒绝信号,则需要
返回$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这样的强大库中,这类事情是不需要的


作为旁注,永远不要抛出字符串:这样会丢失堆栈跟踪。

Angular的
$q
使用一种约定,在这种约定中,抛出的错误会被记录下来,而不管是否被捕获。相反,如果要发信号表示拒绝,则需要
返回$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这样的强大库中,这类事情是不需要的


作为旁注-永远不要抛出字符串:这样会错过堆栈跟踪。

延迟是一种过时的、非常糟糕的构造承诺的方法,使用构造函数解决了这个问题,还有更多:

// 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;
}
用法与承诺构造函数相同:

function f1() {
    return createPromise(function(resolve, reject){
        // code
    });
}

延迟是一种过时的、非常糟糕的构建承诺的方法,使用构造函数解决了这个问题,还有更多:

// 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;
}
用法与承诺构造函数相同:

function f1() {
    return createPromise(function(resolve, reject){
        // code
    });
}
是虫子吗

否。查看会发现创建了一个故意的try/catch块来响应回调中抛出的异常

  • 拒绝承诺,如通过您调用的“推迟”。拒绝
  • 调用已注册的Angular exception hander。如中所示,此操作的默认行为是将其作为错误记录到浏览器控制台,这是您观察到的
  • …还被浏览器的错误处理捕获

    为了澄清,浏览器不会直接处理该异常,但会显示为错误,因为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
      服务的行为方式,如at。您可以将其更改为完全不做任何事情,这样控制台的错误日志中就不会有任何内容,但我不建议这样做

    是虫子吗

    否。查看会发现创建了一个故意的try/catch块来响应回调中抛出的异常

  • 拒绝承诺,如通过您调用的“推迟”。拒绝
  • 调用已注册的Angular exception hander。如中所示,此操作的默认行为是将其作为错误记录到浏览器控制台,这是您观察到的
  • …还被浏览器的错误处理捕获

    为了澄清,浏览器不会直接处理该异常,但会显示为错误,因为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
      服务的行为方式,如at。您可以将其更改为完全不做任何事情,这样控制台的错误日志中就不会有任何内容,但我不建议这样做


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

    下面是一个示例测试,它显示了新的$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');
    
    }));
    
    修正了AngularJS 1.6版 这种行为的理由是,未捕获的错误不同于常规的拒绝,正如 例如,它可能是由编程错误引起的。在实践中,这被证明是令人困惑的 或者对用户来说是不受欢迎的,因为本地promises和任何其他流行的promise库都不受欢迎 区分抛出的错误和常规拒绝。 (注意:虽然这种行为不违背承诺/A+规范,但也没有规定。)

    $q: 由于,从承诺的
    onCompleted
    onRejection
    处理程序引发的错误与常规拒绝处理程序的处理方式完全相同。以前,它还会传递给
    $exceptionHandler()
    (除了以错误为理由拒绝承诺之外)

    新的行为适用于所有人