diff options
-rw-r--r-- | .eslintrc.js | 1 | ||||
-rw-r--r-- | doc/api/cli.md | 8 | ||||
-rw-r--r-- | doc/api/globals.md | 96 | ||||
-rw-r--r-- | doc/node.1 | 3 | ||||
-rw-r--r-- | lib/internal/abort_controller.js | 83 | ||||
-rw-r--r-- | lib/internal/bootstrap/pre_execution.js | 27 | ||||
-rw-r--r-- | lib/internal/main/worker_thread.js | 2 | ||||
-rw-r--r-- | node.gyp | 1 | ||||
-rw-r--r-- | src/node_options.cc | 4 | ||||
-rw-r--r-- | src/node_options.h | 1 | ||||
-rw-r--r-- | test/common/index.js | 1 | ||||
-rw-r--r-- | test/parallel/test-abortcontroller.js | 22 | ||||
-rw-r--r-- | test/parallel/test-bootstrap-modules.js | 7 | ||||
-rw-r--r-- | tools/doc/type-parser.js | 4 |
14 files changed, 260 insertions, 0 deletions
diff --git a/.eslintrc.js b/.eslintrc.js index 8279dfc9c4a..8542d2e77e0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -280,6 +280,7 @@ module.exports = { 'node-core/no-duplicate-requires': 'error', }, globals: { + AbortController: 'readable', Atomics: 'readable', BigInt: 'readable', BigInt64Array: 'readable', diff --git a/doc/api/cli.md b/doc/api/cli.md index 46c2cad5e88..7e317c10c25 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -167,6 +167,13 @@ Enable experimental Source Map V3 support for stack traces. Currently, overriding `Error.prepareStackTrace` is ignored when the `--enable-source-maps` flag is set. +### `--experimental-abortcontroller` +<!-- YAML +added: REPLACEME +--> + +Enable experimental `AbortController` and `AbortSignal` support. + ### `--experimental-import-meta-resolve` <!-- YAML added: @@ -1209,6 +1216,7 @@ Node.js options that are allowed are: * `--disable-proto` * `--enable-fips` * `--enable-source-maps` +* `--experimental-abortcontroller` * `--experimental-import-meta-resolve` * `--experimental-json-modules` * `--experimental-loader` diff --git a/doc/api/globals.md b/doc/api/globals.md index 7e6129e9038..78550035cbc 100644 --- a/doc/api/globals.md +++ b/doc/api/globals.md @@ -17,6 +17,101 @@ The objects listed here are specific to Node.js. There are [built-in objects][] that are part of the JavaScript language itself, which are also globally accessible. +## Class: `AbortController` +<!--YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +<!-- type=global --> + +A utility class used to signal cancelation in selected `Promise`-based APIs. +The API is based on the Web API [`AbortController`][]. + +To use, launch Node.js using the `--experimental-abortcontroller` flag. + +```js +const ac = new AbortController(); + +ac.signal.addEventListener('abort', () => console.log('Aborted!'), + { once: true }); + +ac.abort(); + +console.log(ac.signal.aborted); // Prints True +``` + +### `abortController.abort()` +<!-- YAML +added: REPLACEME +--> + +Triggers the abort signal, causing the `abortController.signal` to emit +the `'abort'` event. + +### `abortController.signal` +<!-- YAML +added: REPLACEME +--> + +* Type: {AbortSignal} + +### Class: `AbortSignal extends EventTarget` +<!-- YAML +added: REPLACEME +--> + +The `AbortSignal` is used to notify observers when the +`abortController.abort()` method is called. + +#### Event: `'abort'` +<!-- YAML +added: REPLACEME +--> + +The `'abort'` event is emitted when the `abortController.abort()` method +is called. The callback is invoked with a single object argument with a +single `type` propety set to `'abort'`: + +```js +const ac = new AbortController(); + +// Use either the onabort property... +ac.signal.onabort = () => console.log('aborted!'); + +// Or the EventTarget API... +ac.signal.addEventListener('abort', (event) => { + console.log(event.type); // Prints 'abort' +}, { once: true }); + +ac.abort(); +``` + +The `AbortController` with which the `AbortSignal` is associated will only +ever trigger the `'abort'` event once. Any event listeners attached to the +`AbortSignal` *should* use the `{ once: true }` option (or, if using the +`EventEmitter` APIs to attach a listener, use the `once()` method) to ensure +that the event listener is removed as soon as the `'abort'` event is handled. +Failure to do so may result in memory leaks. + +#### `abortSignal.aborted` +<!-- YAML +added: REPLACEME +--> + +* Type: {boolean} True after the `AbortController` has been aborted. + +#### `abortSignal.onabort` +<!-- YAML +added: REPLACEME +--> + +* Type: {Function} + +An optional callback function that may be set by user code to be notified +when the `abortController.abort()` function has been called. + ## Class: `Buffer` <!-- YAML added: v0.1.103 @@ -226,6 +321,7 @@ The object that acts as the namespace for all W3C [WebAssembly][webassembly-org] related functionality. See the [Mozilla Developer Network][webassembly-mdn] for usage and compatibility. +[`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController [`TextDecoder`]: util.html#util_class_util_textdecoder [`TextEncoder`]: util.html#util_class_util_textencoder [`URLSearchParams`]: url.html#url_class_urlsearchparams diff --git a/doc/node.1 b/doc/node.1 index 1a217a8bcf9..272b53d4892 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -118,6 +118,9 @@ Enable FIPS-compliant crypto at startup. Requires Node.js to be built with .Sy ./configure --openssl-fips . . +.It Fl -experimental-abortcontroller +Enable experimental AbortController and AbortSignal support. +. .It Fl -enable-source-maps Enable experimental Source Map V3 support for stack traces. . diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js new file mode 100644 index 00000000000..b4ba6dd4f87 --- /dev/null +++ b/lib/internal/abort_controller.js @@ -0,0 +1,83 @@ +'use strict'; + +// Modeled very closely on the AbortController implementation +// in https://github.com/mysticatea/abort-controller (MIT license) + +const { + Object, + Symbol, +} = primordials; + +const { + EventTarget, + Event +} = require('internal/event_target'); +const { + customInspectSymbol, + emitExperimentalWarning +} = require('internal/util'); +const { inspect } = require('internal/util/inspect'); + +const kAborted = Symbol('kAborted'); + +function customInspect(self, obj, depth, options) { + if (depth < 0) + return self; + + const opts = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1 + }); + + return `${self.constructor.name} ${inspect(obj, opts)}`; +} + +class AbortSignal extends EventTarget { + get aborted() { return !!this[kAborted]; } + + [customInspectSymbol](depth, options) { + return customInspect(this, { + aborted: this.aborted + }, depth, options); + } +} + +Object.defineProperties(AbortSignal.prototype, { + aborted: { enumerable: true } +}); + +function abortSignal(signal) { + if (signal[kAborted]) return; + signal[kAborted] = true; + const event = new Event('abort'); + if (typeof signal.onabort === 'function') { + signal.onabort(event); + } + signal.dispatchEvent(event); +} + +class AbortController { + #signal = new AbortSignal(); + + constructor() { + emitExperimentalWarning('AbortController'); + } + + get signal() { return this.#signal; } + abort() { abortSignal(this.#signal); } + + [customInspectSymbol](depth, options) { + return customInspect(this, { + signal: this.signal + }, depth, options); + } +} + +Object.defineProperties(AbortController.prototype, { + signal: { enumerable: true }, + abort: { enumerable: true } +}); + +module.exports = { + AbortController, + AbortSignal, +}; diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index f60814d2dc9..9a0b2c361d3 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -3,6 +3,7 @@ const { Map, ObjectDefineProperty, + ObjectDefineProperties, SafeWeakMap, } = primordials; @@ -53,6 +54,7 @@ function prepareMainThreadExecution(expandArgv1 = false) { // (including preload modules). initializeClusterIPC(); + initializeAbortController(); initializeDeprecations(); initializeWASI(); initializeCJSLoader(); @@ -304,6 +306,30 @@ function initializeDeprecations() { }); } +function initializeAbortController() { + const abortController = getOptionValue('--experimental-abortcontroller'); + if (abortController) { + const { + AbortController, + AbortSignal + } = require('internal/abort_controller'); + ObjectDefineProperties(global, { + AbortController: { + writable: true, + enumerable: false, + configurable: true, + value: AbortController + }, + AbortSignal: { + writable: true, + enumerable: false, + configurable: true, + value: AbortSignal + } + }); + } +} + function setupChildProcessIpcChannel() { if (process.env.NODE_CHANNEL_FD) { const assert = require('internal/assert'); @@ -438,6 +464,7 @@ module.exports = { setupWarningHandler, setupDebugEnv, prepareMainThreadExecution, + initializeAbortController, initializeDeprecations, initializeESMLoader, initializeFrozenIntrinsics, diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index 61045cf1c08..31c23993e80 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -13,6 +13,7 @@ const { setupInspectorHooks, setupWarningHandler, setupDebugEnv, + initializeAbortController, initializeDeprecations, initializeWASI, initializeCJSLoader, @@ -112,6 +113,7 @@ port.on('message', (message) => { if (manifestSrc) { require('internal/process/policy').setup(manifestSrc, manifestURL); } + initializeAbortController(); initializeDeprecations(); initializeWASI(); initializeCJSLoader(); @@ -95,6 +95,7 @@ 'lib/wasi.js', 'lib/worker_threads.js', 'lib/zlib.js', + 'lib/internal/abort_controller.js', 'lib/internal/assert.js', 'lib/internal/assert/assertion_error.js', 'lib/internal/assert/calltracker.js', diff --git a/src/node_options.cc b/src/node_options.cc index cb812f6ccfc..68bd8757cc7 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -274,6 +274,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "experimental Source Map V3 support", &EnvironmentOptions::enable_source_maps, kAllowedInEnvironment); + AddOption("--experimental-abortcontroller", + "experimental AbortController support", + &EnvironmentOptions::experimental_abortcontroller, + kAllowedInEnvironment); AddOption("--experimental-json-modules", "experimental JSON interop support for the ES Module loader", &EnvironmentOptions::experimental_json_modules, diff --git a/src/node_options.h b/src/node_options.h index 54710e48770..b908e639951 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -101,6 +101,7 @@ class EnvironmentOptions : public Options { public: bool abort_on_uncaught_exception = false; bool enable_source_maps = false; + bool experimental_abortcontroller = false; bool experimental_json_modules = false; bool experimental_modules = false; std::string experimental_specifier_resolution; diff --git a/test/common/index.js b/test/common/index.js index f7a29f5d2f4..d3645629a7e 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -256,6 +256,7 @@ function platformTimeout(ms) { } let knownGlobals = [ + AbortController, clearImmediate, clearInterval, clearTimeout, diff --git a/test/parallel/test-abortcontroller.js b/test/parallel/test-abortcontroller.js new file mode 100644 index 00000000000..c1e28e16146 --- /dev/null +++ b/test/parallel/test-abortcontroller.js @@ -0,0 +1,22 @@ +// Flags: --no-warnings --experimental-abortcontroller +'use strict'; + +const common = require('../common'); + +const { ok, strictEqual } = require('assert'); + +{ + const ac = new AbortController(); + ok(ac.signal); + ac.signal.onabort = common.mustCall((event) => { + ok(event); + strictEqual(event.type, 'abort'); + }); + ac.signal.addEventListener('abort', common.mustCall((event) => { + ok(event); + strictEqual(event.type, 'abort'); + }), { once: true }); + ac.abort(); + ac.abort(); + ok(ac.signal.aborted); +} diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 62775b95279..b2506499e7d 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -20,6 +20,7 @@ const expectedModules = new Set([ 'Internal Binding module_wrap', 'Internal Binding native_module', 'Internal Binding options', + 'Internal Binding performance', 'Internal Binding process_methods', 'Internal Binding report', 'Internal Binding string_decoder', @@ -30,9 +31,11 @@ const expectedModules = new Set([ 'Internal Binding types', 'Internal Binding url', 'Internal Binding util', + 'NativeModule async_hooks', 'NativeModule buffer', 'NativeModule events', 'NativeModule fs', + 'NativeModule internal/abort_controller', 'NativeModule internal/assert', 'NativeModule internal/async_hooks', 'NativeModule internal/bootstrap/pre_execution', @@ -42,9 +45,11 @@ const expectedModules = new Set([ 'NativeModule internal/constants', 'NativeModule internal/encoding', 'NativeModule internal/errors', + 'NativeModule internal/event_target', 'NativeModule internal/fixed_queue', 'NativeModule internal/fs/dir', 'NativeModule internal/fs/utils', + 'NativeModule internal/histogram', 'NativeModule internal/idna', 'NativeModule internal/linkedlist', 'NativeModule internal/modules/run_main', @@ -81,8 +86,10 @@ const expectedModules = new Set([ 'NativeModule internal/validators', 'NativeModule internal/vm/module', 'NativeModule path', + 'NativeModule perf_hooks', 'NativeModule timers', 'NativeModule url', + 'NativeModule util', 'NativeModule vm', ]); diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js index ec6c8e54ef8..0a5a795011a 100644 --- a/tools/doc/type-parser.js +++ b/tools/doc/type-parser.js @@ -26,6 +26,10 @@ const customTypesMap = { 'this': `${jsDocPrefix}Reference/Operators/this`, + 'AbortController': 'globals.html#globals_class_abortcontroller', + 'AbortSignal': + 'globals.html#globals_class_abortsignal_extends_eventtarget', + 'ArrayBufferView': 'https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView', |