From 1dd6336652440a4b4fda58be4d31fd2d2ce458e5 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 19 Dec 2016 14:20:32 +0000 Subject: [PATCH] fix($q): Add traceback to unhandled promise rejections, Fixes: #14631 --- src/ng/q.js | 6 ++- test/ng/qSpec.js | 128 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 94 insertions(+), 40 deletions(-) diff --git a/src/ng/q.js b/src/ng/q.js index 305626150d99..de396b2d7c02 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -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); + } } } } diff --git a/test/ng/qSpec.js b/test/ng/qSpec.js index 210a4d983f81..ed1cc0ac95d4 100644 --- a/test/ng/qSpec.js +++ b/test/ng/qSpec.js @@ -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); @@ -2167,45 +2165,97 @@ 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: 'plain value', + 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(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 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 an unhandled rejected promise if disabled', function() { + var defer = q_no_error.defer(); + defer.reject(value); + 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 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([]); + }); + }); }); }); });