0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 20:25:12 -05:00

feat: "rejectionhandled" Web event and "rejectionHandled" Node event (#21875)

This commit adds support for "rejectionhandled" Web Event and
"rejectionHandled" Node event.

```js
import process from "node:process";

process.on("rejectionHandled", (promise) => {
  console.log("rejectionHandled", reason, promise);
});

window.addEventListener("rejectionhandled", (event) => {
  console.log("rejectionhandled", event.reason, event.promise);
});
```

---------

Co-authored-by: Matt Mastracci <matthew@mastracci.com>
This commit is contained in:
Bartek Iwańczuk 2024-01-12 23:10:42 +01:00 committed by GitHub
parent 288774c5ed
commit 7471587d29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 106 additions and 1 deletions

View file

@ -193,3 +193,12 @@ itest!(unhandled_rejection_web_process {
envs: env_vars_for_npm_tests(), envs: env_vars_for_npm_tests(),
http_server: true, 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,
});

View file

@ -3672,6 +3672,11 @@ itest!(unhandled_rejection_dynamic_import2 {
output: "run/unhandled_rejection_dynamic_import2/main.ts.out", 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 { itest!(nested_error {
args: "run run/nested_error/main.ts", args: "run run/nested_error/main.ts",
output: "run/nested_error/main.ts.out", output: "run/nested_error/main.ts.out",

View file

@ -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);

View file

@ -0,0 +1,7 @@
[WILDCARD]
Hello world!
globalThis.addEventListener("unhandledrejection");
Added catch handler to the promise
Web rejectionhandled
Node rejectionHandled
Success

View file

@ -0,0 +1,5 @@
[WILDCARD]
unhandledrejection 1 Promise { <rejected> 1 }
Added catch handler to the promise
rejectionhandled 1 Promise { <rejected> 1 }
Success

View file

@ -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);

View file

@ -12,6 +12,7 @@
declare interface WindowEventMap { declare interface WindowEventMap {
"error": ErrorEvent; "error": ErrorEvent;
"unhandledrejection": PromiseRejectionEvent; "unhandledrejection": PromiseRejectionEvent;
"rejectionhandled": PromiseRejectionEvent;
} }
/** @category Web APIs */ /** @category Web APIs */
@ -25,6 +26,9 @@ declare interface Window extends EventTarget {
onunhandledrejection: onunhandledrejection:
| ((this: Window, ev: PromiseRejectionEvent) => any) | ((this: Window, ev: PromiseRejectionEvent) => any)
| null; | null;
onrejectionhandled:
| ((this: Window, ev: PromiseRejectionEvent) => any)
| null;
close: () => void; close: () => void;
readonly closed: boolean; readonly closed: boolean;
alert: (message?: string) => void; alert: (message?: string) => void;

View file

@ -75,7 +75,6 @@ import { buildAllowedFlags } from "ext:deno_node/internal/process/per_thread.mjs
const notImplementedEvents = [ const notImplementedEvents = [
"multipleResolves", "multipleResolves",
"rejectionHandled",
"worker", "worker",
]; ];
@ -746,6 +745,7 @@ export const removeListener = process.removeListener;
export const removeAllListeners = process.removeAllListeners; export const removeAllListeners = process.removeAllListeners;
let unhandledRejectionListenerCount = 0; let unhandledRejectionListenerCount = 0;
let rejectionHandledListenerCount = 0;
let uncaughtExceptionListenerCount = 0; let uncaughtExceptionListenerCount = 0;
let beforeExitListenerCount = 0; let beforeExitListenerCount = 0;
let exitListenerCount = 0; let exitListenerCount = 0;
@ -755,6 +755,9 @@ process.on("newListener", (event: string) => {
case "unhandledRejection": case "unhandledRejection":
unhandledRejectionListenerCount++; unhandledRejectionListenerCount++;
break; break;
case "rejectionHandled":
rejectionHandledListenerCount++;
break;
case "uncaughtException": case "uncaughtException":
uncaughtExceptionListenerCount++; uncaughtExceptionListenerCount++;
break; break;
@ -775,6 +778,9 @@ process.on("removeListener", (event: string) => {
case "unhandledRejection": case "unhandledRejection":
unhandledRejectionListenerCount--; unhandledRejectionListenerCount--;
break; break;
case "rejectionHandled":
rejectionHandledListenerCount--;
break;
case "uncaughtException": case "uncaughtException":
uncaughtExceptionListenerCount--; uncaughtExceptionListenerCount--;
break; break;
@ -837,6 +843,16 @@ function synchronizeListeners() {
internals.nodeProcessUnhandledRejectionCallback = undefined; 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) { if (uncaughtExceptionListenerCount > 0) {
globalThis.addEventListener("error", processOnError); globalThis.addEventListener("error", processOnError);
} else { } else {

View file

@ -344,6 +344,8 @@ function runtimeStart(
} }
core.setUnhandledPromiseRejectionHandler(processUnhandledPromiseRejection); core.setUnhandledPromiseRejectionHandler(processUnhandledPromiseRejection);
core.setHandledPromiseRejectionHandler(processRejectionHandled);
// Notification that the core received an unhandled promise rejection that is about to // 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. // terminate the runtime. If we can handle it, attempt to do so.
function processUnhandledPromiseRejection(promise, reason) { function processUnhandledPromiseRejection(promise, reason) {
@ -377,6 +379,20 @@ function processUnhandledPromiseRejection(promise, reason) {
return false; 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; let hasBootstrapped = false;
// Delete the `console` object that V8 automaticaly adds onto the global wrapper // 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 // object on context creation. We don't want this console object to shadow the