You are definitely using the promise mechanism incorrectly, throwing a user-defined throw statement does not constitute to catching it as a promise rejection properly. As stated in the $q
documentation:
When comparing deferreds/promises to the familiar behavior of
try/catch/throw, think of reject as the throw keyword in JavaScript.
This also means that if you "catch" an error via a promise error
callback and you want to forward the error to the promise derived from
the current promise, you have to "rethrow" the error by returning a
rejection constructed via reject.
They are similar but not equivalents, to catch user-defined throw statements, you should use catch
statement blocks. While $q
promises should only catch
rejected promises. Thus, in your case, returning a rejected a promise is the proper way of handling the process instead of throwing a user-defined exception.
DEMO
JAVASCRIPT
describe('Q Service Test', function() {
var $q,
$rootScope;
beforeEach(inject(function(_$q_, _$rootScope_) {
$q = _$q_;
$rootScope = _$rootScope_;
}));
it('Rejected promises are handled properly', function() {
var state = 'ok';
$q.when(1)
.then(function() {
return $q.reject('rejected');
})
.catch(function() {
state = 'error';
});
$rootScope.$digest();
expect(state).toBe('error');
});
});
UPDATE:
The reason why your code behaves this way in the browser, is because Angular's $q
implementation uses the try/catch
statement blocks in processing the promise queue. When any of the callbacks throw any errors, it catches the error itself, rejects it with the exception as a reason for rejection, afterwards it uses $exceptionHandler
to log the error. I recommend you to simply return rejected promise.
As for the reason why the unit tests act this way is because of angular-mocks
implementation of the $exceptionHandler
is different with the actual application's $exceptionHandler
. The former creates a provider with different modes, the default angular-mocks implementation uses the rethrow
mode which in turn throws the exception instead of logging it. If you want to have your unit tests behave the same way as how the default application's $exceptionHandler
then you can set the mode to 'log'
.
DEMO
JAVASCRIPT
describe('Q Service Test', function() {
var $q,
$rootScope;
beforeEach(module('ng', function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
}));
beforeEach(inject(function(_$q_, _$rootScope_) {
$q = _$q_;
$rootScope = _$rootScope_;
}));
it('Caught exceptions are handled properly', function() {
var state = 'ok';
$q.when(1)
.then(function() {
throw new Error();
})
.catch(function() {
state = 'error';
});
$rootScope.$digest();
expect(state).toBe('error');
});
});