Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix($q): Add traceback to unhandled promise rejections
Browse files Browse the repository at this point in the history
Fixes #14631
Closes #15527
  • Loading branch information
graingert authored and mgol committed Dec 21, 2016
1 parent f768da2 commit 316f60f
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 40 deletions.
6 changes: 5 additions & 1 deletion src/ng/q.js
Expand Up @@ -381,7 +381,11 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
if (!toCheck.pur) {
toCheck.pur = true;
var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value);
exceptionHandler(errorMessage);
if (toCheck.value instanceof Error) {
exceptionHandler(toCheck.value, errorMessage);
} else {
exceptionHandler(errorMessage);
}
}
}
}
Expand Down
129 changes: 90 additions & 39 deletions test/ng/qSpec.js
Expand Up @@ -181,16 +181,14 @@ describe('q', function() {
};


function exceptionHandler(reason) {
exceptionHandlerCalls.push(reason);
}


function exceptionHandlerStr() {
return exceptionHandlerCalls.join('; ');
function exceptionHandler(exception, reason) {
if (typeof reason === 'undefined') {
exceptionHandlerCalls.push({ reason: exception });
} else {
exceptionHandlerCalls.push({ reason: reason, exception: exception });
}
}


beforeEach(function() {
q = qFactory(mockNextTick.nextTick, exceptionHandler, true);
q_no_error = qFactory(mockNextTick.nextTick, exceptionHandler, false);
Expand Down Expand Up @@ -2167,45 +2165,98 @@ describe('q', function() {


describe('when exceptionHandler is called', function() {
it('should log an unhandled rejected promise', function() {
var defer = q.defer();
defer.reject('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('Possibly unhandled rejection: foo');
});
function CustomError() { }
CustomError.prototype = Object.create(Error.prototype);

var errorEg = new Error('Fail');
var errorStr = toDebugString(errorEg);

it('should not log an unhandled rejected promise if disabled', function() {
var defer = q_no_error.defer();
defer.reject('foo');
expect(exceptionHandlerStr()).toBe('');
});
var customError = new CustomError('Custom');
var customErrorStr = toDebugString(customError);

var nonErrorObj = { isATest: 'this is' };
var nonErrorObjStr = toDebugString(nonErrorObj);

it('should log a handled rejected promise on a promise without rejection callbacks', function() {
var defer = q.defer();
defer.promise.then(noop);
defer.reject('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('Possibly unhandled rejection: foo');
});
var fixtures = [
{
type: 'Error object',
value: errorEg,
expected: {
exception: errorEg,
reason: 'Possibly unhandled rejection: ' + errorStr
}
},
{
type: 'custom Error object',
value: customError,
expected: {
exception: customError,
reason: 'Possibly unhandled rejection: ' + customErrorStr
}
},
{
type: 'non-Error object',
value: nonErrorObj,
expected: {
reason: 'Possibly unhandled rejection: ' + nonErrorObjStr
}
},
{
type: 'string primitive',
value: 'foo',
expected: {
reason: 'Possibly unhandled rejection: foo'
}
}
];
forEach(fixtures, function(fixture) {
var type = fixture.type;
var value = fixture.value;
var expected = fixture.expected;

describe('with ' + type, function() {

it('should not log a handled rejected promise', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.reject('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('');
});
it('should log an unhandled rejected promise', function() {
var defer = q.defer();
defer.reject(value);
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([expected]);
});


it('should not log a handled rejected promise that is handled in a future tick', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.resolve(q.reject('foo'));
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('');
it('should not log an unhandled rejected promise if disabled', function() {
var defer = q_no_error.defer();
defer.reject(value);
expect(exceptionHandlerCalls).toEqual([]);
});


it('should log a handled rejected promise on a promise without rejection callbacks', function() {
var defer = q.defer();
defer.promise.then(noop);
defer.reject(value);
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([expected]);
});


it('should not log a handled rejected promise', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.reject(value);
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([]);
});


it('should not log a handled rejected promise that is handled in a future tick', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.resolve(q.reject(value));
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([]);
});
});
});
});
});

0 comments on commit 316f60f

Please sign in to comment.