mirror of
https://github.com/nodejs/node.git
synced 2025-12-28 07:50:41 +00:00
lib: initial experimental AbortController implementation
AbortController impl based very closely on: https://github.com/mysticatea/abort-controller Marked experimental. Not currently used by any of the existing promise apis. Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/33527 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
3e2a300710
commit
74ca960aac
@ -280,6 +280,7 @@ module.exports = {
|
||||
'node-core/no-duplicate-requires': 'error',
|
||||
},
|
||||
globals: {
|
||||
AbortController: 'readable',
|
||||
Atomics: 'readable',
|
||||
BigInt: 'readable',
|
||||
BigInt64Array: 'readable',
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
.
|
||||
|
||||
83
lib/internal/abort_controller.js
Normal file
83
lib/internal/abort_controller.js
Normal file
@ -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,
|
||||
};
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
1
node.gyp
1
node.gyp
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -256,6 +256,7 @@ function platformTimeout(ms) {
|
||||
}
|
||||
|
||||
let knownGlobals = [
|
||||
AbortController,
|
||||
clearImmediate,
|
||||
clearInterval,
|
||||
clearTimeout,
|
||||
|
||||
22
test/parallel/test-abortcontroller.js
Normal file
22
test/parallel/test-abortcontroller.js
Normal file
@ -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);
|
||||
}
|
||||
@ -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',
|
||||
]);
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user