diff --git a/benchmark/events/abortcontroller-abort.js b/benchmark/events/abortcontroller-abort.js new file mode 100644 index 00000000000000..c3a675be10173b --- /dev/null +++ b/benchmark/events/abortcontroller-abort.js @@ -0,0 +1,16 @@ +'use strict'; +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + n: [1e6], +}); + +function main({ n }) { + bench.start(); + for (let i = 0; i < n; i++) { + const ac = new AbortController(); + ac.signal.addEventListener('abort', () => {}); + ac.abort(); + } + bench.end(n); +} diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 2c1f43354f9f7c..e4c0633c5ef945 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -4,6 +4,7 @@ // in https://github.com/mysticatea/abort-controller (MIT license) const { + ErrorCaptureStackTrace, ObjectAssign, ObjectDefineProperties, ObjectSetPrototypeOf, @@ -63,6 +64,7 @@ const { let _MessageChannel; let makeTransferable; +let defaultDOMException; // Loading the MessageChannel and makeTransferable have to be done lazily // because otherwise we'll end up with a require cycle that ends up with @@ -79,6 +81,19 @@ function lazyMakeTransferable(obj) { return makeTransferable(obj); } +function lazyDOMException() { + if (defaultDOMException) { + return defaultDOMException; + } + + defaultDOMException = new DOMException('This operation was aborted', 'AbortError'); + + // Avoid V8 leak + // eslint-disable-next-line no-unused-vars + const dummy = defaultDOMException.stack; + return defaultDOMException; +} + const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout); const timeOutSignals = new SafeSet(); @@ -166,8 +181,11 @@ class AbortSignal extends EventTarget { * @param {any} [reason] * @returns {AbortSignal} */ - static abort( - reason = new DOMException('This operation was aborted', 'AbortError')) { + static abort(reason = undefined) { + if (reason === undefined) { + reason = lazyDOMException(); + ErrorCaptureStackTrace(reason); + } return createAbortSignal({ aborted: true, reason }); } @@ -328,7 +346,11 @@ class AbortController { /** * @param {any} [reason] */ - abort(reason = new DOMException('This operation was aborted', 'AbortError')) { + abort(reason) { + if (reason === undefined) { + reason = lazyDOMException(); + ErrorCaptureStackTrace(reason); + } abortSignal(this.#signal ??= createAbortSignal(), reason); }