AngularJS - Promises rethrow caught exceptions

VitalyB picture VitalyB · Apr 27, 2014 · Viewed 15.3k times · Source

In the following code, an exception is caught by the catch function of the $q promise:

// 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:

enter image description here

The exception was caught in Angular, but was also caught by the error handling of the browser. This behavior does reproduce with Q library.

Is it a bug? How can I truly catch an exception with $q?

Answer

Benjamin Gruenbaum picture Benjamin Gruenbaum · Apr 27, 2014

Angular's $q uses a convention where thrown errors are logged regardless of being caught. Instead, if you want to signal a rejection you need to return $q.reject(... as such:

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.

As a side note - never, ever throw strings : you miss on stack traces that way.