summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/integration/node_unit_tests.rs9
-rw-r--r--cli/tests/integration/run_tests.rs5
-rw-r--r--cli/tests/testdata/node/rejection_handled_web_process.ts26
-rw-r--r--cli/tests/testdata/node/rejection_handled_web_process.ts.out7
-rw-r--r--cli/tests/testdata/run/rejection_handled.out5
-rw-r--r--cli/tests/testdata/run/rejection_handled.ts17
-rw-r--r--cli/tsc/dts/lib.deno.window.d.ts4
-rw-r--r--ext/node/polyfills/process.ts18
-rw-r--r--runtime/js/99_main.js16
9 files changed, 106 insertions, 1 deletions
diff --git a/cli/tests/integration/node_unit_tests.rs b/cli/tests/integration/node_unit_tests.rs
index 273066b09..351bf1eec 100644
--- a/cli/tests/integration/node_unit_tests.rs
+++ b/cli/tests/integration/node_unit_tests.rs
@@ -193,3 +193,12 @@ itest!(unhandled_rejection_web_process {
envs: env_vars_for_npm_tests(),
http_server: true,
});
+
+// Ensure that Web `onrejectionhandled` is fired before
+// Node's `process.on('rejectionHandled')`.
+itest!(rejection_handled_web_process {
+ args: "run -A node/rejection_handled_web_process.ts",
+ output: "node/rejection_handled_web_process.ts.out",
+ envs: env_vars_for_npm_tests(),
+ http_server: true,
+});
diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs
index 2a349a5f2..999dc1177 100644
--- a/cli/tests/integration/run_tests.rs
+++ b/cli/tests/integration/run_tests.rs
@@ -3672,6 +3672,11 @@ itest!(unhandled_rejection_dynamic_import2 {
output: "run/unhandled_rejection_dynamic_import2/main.ts.out",
});
+itest!(rejection_handled {
+ args: "run --check run/rejection_handled.ts",
+ output: "run/rejection_handled.out",
+});
+
itest!(nested_error {
args: "run run/nested_error/main.ts",
output: "run/nested_error/main.ts.out",
diff --git a/cli/tests/testdata/node/rejection_handled_web_process.ts b/cli/tests/testdata/node/rejection_handled_web_process.ts
new file mode 100644
index 000000000..00d943feb
--- /dev/null
+++ b/cli/tests/testdata/node/rejection_handled_web_process.ts
@@ -0,0 +1,26 @@
+import chalk from "npm:chalk";
+import process from "node:process";
+
+console.log(chalk.red("Hello world!"));
+
+globalThis.addEventListener("unhandledrejection", (e) => {
+ console.log('globalThis.addEventListener("unhandledrejection");');
+ e.preventDefault();
+});
+
+globalThis.addEventListener("rejectionhandled", (_) => {
+ console.log("Web rejectionhandled");
+});
+
+process.on("rejectionHandled", (_) => {
+ console.log("Node rejectionHandled");
+});
+
+const a = Promise.reject(1);
+setTimeout(() => {
+ a.catch(() => console.log("Added catch handler to the promise"));
+}, 10);
+
+setTimeout(() => {
+ console.log("Success");
+}, 50);
diff --git a/cli/tests/testdata/node/rejection_handled_web_process.ts.out b/cli/tests/testdata/node/rejection_handled_web_process.ts.out
new file mode 100644
index 000000000..3a4e2ac23
--- /dev/null
+++ b/cli/tests/testdata/node/rejection_handled_web_process.ts.out
@@ -0,0 +1,7 @@
+[WILDCARD]
+Hello world!
+globalThis.addEventListener("unhandledrejection");
+Added catch handler to the promise
+Web rejectionhandled
+Node rejectionHandled
+Success
diff --git a/cli/tests/testdata/run/rejection_handled.out b/cli/tests/testdata/run/rejection_handled.out
new file mode 100644
index 000000000..5c06fcd2b
--- /dev/null
+++ b/cli/tests/testdata/run/rejection_handled.out
@@ -0,0 +1,5 @@
+[WILDCARD]
+unhandledrejection 1 Promise { <rejected> 1 }
+Added catch handler to the promise
+rejectionhandled 1 Promise { <rejected> 1 }
+Success
diff --git a/cli/tests/testdata/run/rejection_handled.ts b/cli/tests/testdata/run/rejection_handled.ts
new file mode 100644
index 000000000..f058ff966
--- /dev/null
+++ b/cli/tests/testdata/run/rejection_handled.ts
@@ -0,0 +1,17 @@
+window.addEventListener("unhandledrejection", (event) => {
+ console.log("unhandledrejection", event.reason, event.promise);
+ event.preventDefault();
+});
+
+window.addEventListener("rejectionhandled", (event) => {
+ console.log("rejectionhandled", event.reason, event.promise);
+});
+
+const a = Promise.reject(1);
+setTimeout(async () => {
+ a.catch(() => console.log("Added catch handler to the promise"));
+}, 10);
+
+setTimeout(() => {
+ console.log("Success");
+}, 50);
diff --git a/cli/tsc/dts/lib.deno.window.d.ts b/cli/tsc/dts/lib.deno.window.d.ts
index c518c5356..eaab7c3c2 100644
--- a/cli/tsc/dts/lib.deno.window.d.ts
+++ b/cli/tsc/dts/lib.deno.window.d.ts
@@ -12,6 +12,7 @@
declare interface WindowEventMap {
"error": ErrorEvent;
"unhandledrejection": PromiseRejectionEvent;
+ "rejectionhandled": PromiseRejectionEvent;
}
/** @category Web APIs */
@@ -25,6 +26,9 @@ declare interface Window extends EventTarget {
onunhandledrejection:
| ((this: Window, ev: PromiseRejectionEvent) => any)
| null;
+ onrejectionhandled:
+ | ((this: Window, ev: PromiseRejectionEvent) => any)
+ | null;
close: () => void;
readonly closed: boolean;
alert: (message?: string) => void;
diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts
index 3d5009b90..1edcccc00 100644
--- a/ext/node/polyfills/process.ts
+++ b/ext/node/polyfills/process.ts
@@ -75,7 +75,6 @@ import { buildAllowedFlags } from "ext:deno_node/internal/process/per_thread.mjs
const notImplementedEvents = [
"multipleResolves",
- "rejectionHandled",
"worker",
];
@@ -746,6 +745,7 @@ export const removeListener = process.removeListener;
export const removeAllListeners = process.removeAllListeners;
let unhandledRejectionListenerCount = 0;
+let rejectionHandledListenerCount = 0;
let uncaughtExceptionListenerCount = 0;
let beforeExitListenerCount = 0;
let exitListenerCount = 0;
@@ -755,6 +755,9 @@ process.on("newListener", (event: string) => {
case "unhandledRejection":
unhandledRejectionListenerCount++;
break;
+ case "rejectionHandled":
+ rejectionHandledListenerCount++;
+ break;
case "uncaughtException":
uncaughtExceptionListenerCount++;
break;
@@ -775,6 +778,9 @@ process.on("removeListener", (event: string) => {
case "unhandledRejection":
unhandledRejectionListenerCount--;
break;
+ case "rejectionHandled":
+ rejectionHandledListenerCount--;
+ break;
case "uncaughtException":
uncaughtExceptionListenerCount--;
break;
@@ -837,6 +843,16 @@ function synchronizeListeners() {
internals.nodeProcessUnhandledRejectionCallback = undefined;
}
+ // Install special "handledrejection" handler, that will be called
+ // last.
+ if (rejectionHandledListenerCount > 0) {
+ internals.nodeProcessRejectionHandledCallback = (event) => {
+ process.emit("rejectionHandled", event.reason, event.promise);
+ };
+ } else {
+ internals.nodeProcessRejectionHandledCallback = undefined;
+ }
+
if (uncaughtExceptionListenerCount > 0) {
globalThis.addEventListener("error", processOnError);
} else {
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 4644d2d08..6c5ca3b59 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -344,6 +344,8 @@ function runtimeStart(
}
core.setUnhandledPromiseRejectionHandler(processUnhandledPromiseRejection);
+core.setHandledPromiseRejectionHandler(processRejectionHandled);
+
// Notification that the core received an unhandled promise rejection that is about to
// terminate the runtime. If we can handle it, attempt to do so.
function processUnhandledPromiseRejection(promise, reason) {
@@ -377,6 +379,20 @@ function processUnhandledPromiseRejection(promise, reason) {
return false;
}
+function processRejectionHandled(promise, reason) {
+ const rejectionHandledEvent = new event.PromiseRejectionEvent(
+ "rejectionhandled",
+ { promise, reason },
+ );
+
+ // Note that the handler may throw, causing a recursive "error" event
+ globalThis_.dispatchEvent(rejectionHandledEvent);
+
+ if (typeof internals.nodeProcessRejectionHandledCallback !== "undefined") {
+ internals.nodeProcessRejectionHandledCallback(rejectionHandledEvent);
+ }
+}
+
let hasBootstrapped = false;
// Delete the `console` object that V8 automaticaly adds onto the global wrapper
// object on context creation. We don't want this console object to shadow the