diff --git a/CHANGES.md b/CHANGES.md index 2d845a31..95517ee6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Next minor change: + - Ensure that unhandled rejection strings created from actual `Error`s in all modern browsers include the error type and message (in addition to the stack). + - Add a hook, `Q.customizeRejectionString` which can be used for developers to customize how rejection values are string-ified. + ## 1.5.0 - Q.any gives an error message from the last rejected promise diff --git a/q.js b/q.js index 6e467958..10237544 100644 --- a/q.js +++ b/q.js @@ -1083,10 +1083,25 @@ function trackRejection(promise, reason) { } unhandledRejections.push(promise); + var rejectionString = reason + ""; + + // Public hook to allow for custom tweaking of unhandled rejection string (note, stack + // will be appended afterwards by code below) + if (Q.customizeRejectionString) { + rejectionString = Q.customizeRejectionString(reason); + } + if (reason && typeof reason.stack !== "undefined") { - unhandledReasons.push(reason.stack); + // If the error's stack string already includes the error type and message, don't double it up + // (since only a few browsers do that, e.g. Chrome vs Firefox). Otherwise, manually append + // the error type and message to the stack string + if (reason.stack.slice && reason.stack.slice(0, rejectionString.length) === rejectionString) { + unhandledReasons.push(reason.stack); + } else { + unhandledReasons.push(rejectionString + "\n" + reason.stack); + } } else { - unhandledReasons.push("(no stack) " + reason); + unhandledReasons.push("(no stack) " + rejectionString); } } diff --git a/spec/q-spec.js b/spec/q-spec.js index 6d4068b5..fb93ef7e 100644 --- a/spec/q-spec.js +++ b/spec/q-spec.js @@ -2948,9 +2948,18 @@ describe("unhandled rejection reporting", function () { it("reports a stack trace", function () { var error = new Error("a reason"); + var firstLineOfStack = error.stack ? error.stack.split('\n')[0] : undefined; + var expectedStack; + Q.reject(error); - expect(Q.getUnhandledReasons()).toEqual([error.stack]); + if (firstLineOfStack && firstLineOfStack.indexOf('a reason') >= 0) { + expectedStack = error.stack; + } else { + // For browsers like firefox that don't include the error type/message in the stack + expectedStack = "Error: a reason\n" + error.stack; + } + expect(Q.getUnhandledReasons()).toEqual([expectedStack]); }); it("doesn't let you mutate the internal array", function () { @@ -2976,4 +2985,38 @@ describe("unhandled rejection reporting", function () { expect(Q.getUnhandledReasons()).toEqual([]); }); + + describe("Q.customizeRejectionString", function() { + beforeEach(function() { + var spy = jasmine.createSpy(); + Q.customizeRejectionString = spy; + }) + + afterEach(function() { + delete Q.customizeRejectionString; + }) + + it("is called if it exists", function () { + Q.reject('no reason'); + expect(Q.customizeRejectionString).toHaveBeenCalled(); + }); + + it("is called with the rejection reason", function () { + Q.reject('no reason 2'); + expect(Q.customizeRejectionString).toHaveBeenCalledWith('no reason 2'); + }); + + it("its result changes what is stored in the unhandled reasons array", function () { + Q.customizeRejectionString.andReturn('changed reason'); + Q.reject('no reason 3'); + expect(Q.getUnhandledReasons()).toEqual(['(no stack) changed reason']); + }); + + it("its does't remove the stack when an error is rejected", function () { + var fakeError = new Error('fake errror'); + Q.customizeRejectionString.andReturn('Even fancier fake error message'); + Q.reject(fakeError); + expect(Q.getUnhandledReasons()[0]).toEqual('Even fancier fake error message' + '\n' + fakeError.stack); + }); + }); });