Skip to content

Commit 25a1fff

Browse files
feat: Ignore unhandled promise rejections that lack a valid reason (#1233)
1 parent 5c619d9 commit 25a1fff

File tree

5 files changed

+60
-16
lines changed

5 files changed

+60
-16
lines changed

src/features/jserrors/shared/cast-error.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@ export function castError (error) {
3131
* @returns {Error} An Error object with the message as the casted reason
3232
*/
3333
export function castPromiseRejectionEvent (promiseRejectionEvent) {
34-
let prefix = 'Unhandled Promise Rejection'
34+
const prefix = 'Unhandled Promise Rejection'
3535

36-
if (canTrustError(promiseRejectionEvent?.reason)) {
36+
/**
37+
* If the casted return value is falsy like this, it will get dropped and not produce an error event for harvest.
38+
* We drop promise rejections that could not form a valid error stack or message deriving from the .reason attribute
39+
* -- such as a manually invoked rejection without an argument -- since they lack reproduction value and create confusion.
40+
* */
41+
if (!promiseRejectionEvent?.reason) return
42+
43+
if (canTrustError(promiseRejectionEvent.reason)) {
3744
try {
3845
promiseRejectionEvent.reason.message = prefix + ': ' + promiseRejectionEvent.reason.message
3946
return castError(promiseRejectionEvent.reason)
@@ -42,8 +49,6 @@ export function castPromiseRejectionEvent (promiseRejectionEvent) {
4249
}
4350
}
4451

45-
if (typeof promiseRejectionEvent.reason === 'undefined') return castError(prefix)
46-
4752
const error = castError(promiseRejectionEvent.reason)
4853
error.message = prefix + ': ' + error?.message
4954
return error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE html>
2+
<!--
3+
Copyright 2020 New Relic Corporation.
4+
PDX-License-Identifier: Apache-2.0
5+
-->
6+
<html>
7+
<head>
8+
<title>RUM Unit Test</title>
9+
{init} {config} {loader}
10+
</head>
11+
12+
<body>
13+
<script>
14+
"use strict";
15+
// falsy reasons, these should get dropped
16+
new Promise(function(res, rej) { rej() });
17+
new Promise(function(res, rej) { rej('') });
18+
new Promise(function(res, rej) { rej(null) });
19+
</script>
20+
this is a generic page that is instrumented by the JS agent
21+
</body>
22+
</html>

tests/assets/unhandled-promise-rejection-readable.html

+8
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@
5252
}
5353
rej(new Foo());
5454
});
55+
new Promise(function(res, rej) {
56+
throw new Error("test");
57+
});
58+
new Promise(function(res, rej) {
59+
(function () {
60+
throw new Error("test");
61+
})()
62+
});
5563
</script>
5664
this is a generic page that is instrumented by the JS agent
5765
</body>

tests/specs/err/scripts-and-callbacks.e2e.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,13 @@ describe('JSE Error detection in various callbacks', () => {
159159
{ message: 'Unhandled Promise Rejection: [1,2,3]', meta: 'array' },
160160
{ message: 'Unhandled Promise Rejection: test', meta: 'error with message' },
161161
{ message: 'test', meta: 'error with no setter with message' },
162-
{ message: 'Unhandled Promise Rejection', meta: 'undefined' },
163-
{ message: 'Unhandled Promise Rejection: null', meta: 'null' },
164162
{ message: 'Unhandled Promise Rejection: ', meta: 'error with no message' },
165163
{ message: 'Unhandled Promise Rejection: {}', meta: 'map object' },
166164
{ message: 'Unhandled Promise Rejection: {"abc":"Hello"}', meta: 'factory function' },
167165
{ message: 'Unhandled Promise Rejection: ', meta: 'uncalled function' },
168-
{ message: 'Unhandled Promise Rejection: {"abc":"circular"}', meta: 'circular object' }
166+
{ message: 'Unhandled Promise Rejection: {"abc":"circular"}', meta: 'circular object' },
167+
{ message: 'Unhandled Promise Rejection: test', meta: 'thrown error' },
168+
{ message: 'Unhandled Promise Rejection: test', meta: 'thrown error in nested fn' }
169169
]
170170
expectedErrorMessages.forEach(expected => {
171171
it('should report unhandledPromiseRejections that are readable - ' + expected.meta, async () => {
@@ -189,6 +189,17 @@ describe('JSE Error detection in various callbacks', () => {
189189
})
190190
})
191191

192+
it('should not report unhandled rejections with falsy reasons', async () => {
193+
const pageUrl = await browser.testHandle.assetURL('unhandled-promise-rejection-falsy-reason.html')
194+
const [errorsExpect] = await Promise.all([
195+
errorsCapture.waitForResult({ timeout: 10000 }),
196+
browser.url(pageUrl)
197+
.then(() => browser.waitForAgentLoad())
198+
])
199+
200+
expect(errorsExpect).toEqual([])
201+
})
202+
192203
it('should report errors from XHR callbacks', async () => {
193204
const pageUrl = await browser.testHandle.assetURL('xhr-error.html')
194205
const [[{ request: { body: errorBody, query: errorQuery } }]] = await Promise.all([

tests/unit/features/jserrors/shared/cast-error.test.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,13 @@ describe('cast-error', () => {
133133
})
134134
})
135135

136-
test('handles events with an undefined reason', () => {
137-
const castedError = castPromiseRejectionEvent({ reason: undefined })
138-
expect(castedError).toMatchObject({
139-
name: 'UncaughtError',
140-
message: 'Unhandled Promise Rejection',
141-
sourceURL: undefined,
142-
line: undefined,
143-
column: undefined
144-
})
136+
test('terminates for events with falsy reasons', () => {
137+
let castedError = castPromiseRejectionEvent({ reason: undefined })
138+
expect(castedError).toBeUndefined()
139+
castedError = castPromiseRejectionEvent({ reason: null })
140+
expect(castedError).toBeUndefined()
141+
castedError = castPromiseRejectionEvent({ reason: '' })
142+
expect(castedError).toBeUndefined()
145143
})
146144
})
147145
})

0 commit comments

Comments
 (0)