fix($q): Add traceback to unhandled promise rejections

Fixes #14631
Closes #15527
This commit is contained in:
Thomas Grainger
2016-12-19 14:20:32 +00:00
committed by Michał Gołębiowski
parent f768da2488
commit 316f60fd14
2 changed files with 95 additions and 40 deletions
+5 -1
View File
@@ -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);
}
}
}
}
+90 -39
View File
@@ -181,16 +181,14 @@ describe('q', function() {
};
function exceptionHandler(reason) {
exceptionHandlerCalls.push(reason);
function exceptionHandler(exception, reason) {
if (typeof reason === 'undefined') {
exceptionHandlerCalls.push({ reason: exception });
} else {
exceptionHandlerCalls.push({ reason: reason, exception: exception });
}
}
function exceptionHandlerStr() {
return exceptionHandlerCalls.join('; ');
}
beforeEach(function() {
q = qFactory(mockNextTick.nextTick, exceptionHandler, true);
q_no_error = qFactory(mockNextTick.nextTick, exceptionHandler, false);
@@ -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);
var customError = new CustomError('Custom');
var customErrorStr = toDebugString(customError);
var nonErrorObj = { isATest: 'this is' };
var nonErrorObjStr = toDebugString(nonErrorObj);
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 log an unhandled rejected promise', function() {
var defer = q.defer();
defer.reject(value);
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([expected]);
});
it('should not log an unhandled rejected promise if disabled', function() {
var defer = q_no_error.defer();
defer.reject('foo');
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('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('Possibly unhandled rejection: foo');
});
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('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('');
});
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('foo'));
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('');
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([]);
});
});
});
});
});