Skip to content

Commit f30f931

Browse files
jasnelltargos
authored andcommitted
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 <[email protected]> PR-URL: #33527 Backport-PR-URL: #38386 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 8d4936d commit f30f931

File tree

12 files changed

+252
-0
lines changed

12 files changed

+252
-0
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ module.exports = {
282282
'node-core/no-duplicate-requires': 'error',
283283
},
284284
globals: {
285+
AbortController: 'readable',
285286
Atomics: 'readable',
286287
BigInt: 'readable',
287288
BigInt64Array: 'readable',

doc/api/cli.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ Enable experimental Source Map v3 support for stack traces.
197197
Currently, overriding `Error.prepareStackTrace` is ignored when the
198198
`--enable-source-maps` flag is set.
199199

200+
### `--experimental-abortcontroller`
201+
<!-- YAML
202+
added: REPLACEME
203+
-->
204+
205+
Enable experimental `AbortController` and `AbortSignal` support.
206+
200207
### `--experimental-import-meta-resolve`
201208
<!-- YAML
202209
added:
@@ -1222,6 +1229,7 @@ Node.js options that are allowed are:
12221229
* `--disable-proto`
12231230
* `--enable-fips`
12241231
* `--enable-source-maps`
1232+
* `--experimental-abortcontroller`
12251233
* `--experimental-import-meta-resolve`
12261234
* `--experimental-json-modules`
12271235
* `--experimental-loader`

doc/api/globals.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,101 @@ The objects listed here are specific to Node.js. There are [built-in objects][]
1717
that are part of the JavaScript language itself, which are also globally
1818
accessible.
1919

20+
## Class: `AbortController`
21+
<!--YAML
22+
added: REPLACEME
23+
-->
24+
25+
> Stability: 1 - Experimental
26+
27+
<!-- type=global -->
28+
29+
A utility class used to signal cancelation in selected `Promise`-based APIs.
30+
The API is based on the Web API [`AbortController`][].
31+
32+
To use, launch Node.js using the `--experimental-abortcontroller` flag.
33+
34+
```js
35+
const ac = new AbortController();
36+
37+
ac.signal.addEventListener('abort', () => console.log('Aborted!'),
38+
{ once: true });
39+
40+
ac.abort();
41+
42+
console.log(ac.signal.aborted); // Prints True
43+
```
44+
45+
### `abortController.abort()`
46+
<!-- YAML
47+
added: REPLACEME
48+
-->
49+
50+
Triggers the abort signal, causing the `abortController.signal` to emit
51+
the `'abort'` event.
52+
53+
### `abortController.signal`
54+
<!-- YAML
55+
added: REPLACEME
56+
-->
57+
58+
* Type: {AbortSignal}
59+
60+
### Class: `AbortSignal extends EventTarget`
61+
<!-- YAML
62+
added: REPLACEME
63+
-->
64+
65+
The `AbortSignal` is used to notify observers when the
66+
`abortController.abort()` method is called.
67+
68+
#### Event: `'abort'`
69+
<!-- YAML
70+
added: REPLACEME
71+
-->
72+
73+
The `'abort'` event is emitted when the `abortController.abort()` method
74+
is called. The callback is invoked with a single object argument with a
75+
single `type` propety set to `'abort'`:
76+
77+
```js
78+
const ac = new AbortController();
79+
80+
// Use either the onabort property...
81+
ac.signal.onabort = () => console.log('aborted!');
82+
83+
// Or the EventTarget API...
84+
ac.signal.addEventListener('abort', (event) => {
85+
console.log(event.type); // Prints 'abort'
86+
}, { once: true });
87+
88+
ac.abort();
89+
```
90+
91+
The `AbortController` with which the `AbortSignal` is associated will only
92+
ever trigger the `'abort'` event once. Any event listeners attached to the
93+
`AbortSignal` *should* use the `{ once: true }` option (or, if using the
94+
`EventEmitter` APIs to attach a listener, use the `once()` method) to ensure
95+
that the event listener is removed as soon as the `'abort'` event is handled.
96+
Failure to do so may result in memory leaks.
97+
98+
#### `abortSignal.aborted`
99+
<!-- YAML
100+
added: REPLACEME
101+
-->
102+
103+
* Type: {boolean} True after the `AbortController` has been aborted.
104+
105+
#### `abortSignal.onabort`
106+
<!-- YAML
107+
added: REPLACEME
108+
-->
109+
110+
* Type: {Function}
111+
112+
An optional callback function that may be set by user code to be notified
113+
when the `abortController.abort()` function has been called.
114+
20115
## Class: `Buffer`
21116
<!-- YAML
22117
added: v0.1.103
@@ -226,6 +321,7 @@ The object that acts as the namespace for all W3C
226321
[WebAssembly][webassembly-org] related functionality. See the
227322
[Mozilla Developer Network][webassembly-mdn] for usage and compatibility.
228323

324+
[`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
229325
[`TextDecoder`]: util.md#util_class_util_textdecoder
230326
[`TextEncoder`]: util.md#util_class_util_textencoder
231327
[`URLSearchParams`]: url.md#url_class_urlsearchparams

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ Enable FIPS-compliant crypto at startup.
136136
Requires Node.js to be built with
137137
.Sy ./configure --openssl-fips .
138138
.
139+
.It Fl -experimental-abortcontroller
140+
Enable experimental AbortController and AbortSignal support.
141+
.
139142
.It Fl -enable-source-maps
140143
Enable experimental Source Map V3 support for stack traces.
141144
.

lib/internal/abort_controller.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use strict';
2+
3+
// Modeled very closely on the AbortController implementation
4+
// in https://github.com/mysticatea/abort-controller (MIT license)
5+
6+
const {
7+
Object,
8+
Symbol,
9+
} = primordials;
10+
11+
const {
12+
EventTarget,
13+
Event
14+
} = require('internal/event_target');
15+
const {
16+
customInspectSymbol,
17+
emitExperimentalWarning
18+
} = require('internal/util');
19+
const { inspect } = require('internal/util/inspect');
20+
21+
const kAborted = Symbol('kAborted');
22+
23+
function customInspect(self, obj, depth, options) {
24+
if (depth < 0)
25+
return self;
26+
27+
const opts = Object.assign({}, options, {
28+
depth: options.depth === null ? null : options.depth - 1
29+
});
30+
31+
return `${self.constructor.name} ${inspect(obj, opts)}`;
32+
}
33+
34+
class AbortSignal extends EventTarget {
35+
get aborted() { return !!this[kAborted]; }
36+
37+
[customInspectSymbol](depth, options) {
38+
return customInspect(this, {
39+
aborted: this.aborted
40+
}, depth, options);
41+
}
42+
}
43+
44+
Object.defineProperties(AbortSignal.prototype, {
45+
aborted: { enumerable: true }
46+
});
47+
48+
function abortSignal(signal) {
49+
if (signal[kAborted]) return;
50+
signal[kAborted] = true;
51+
const event = new Event('abort');
52+
if (typeof signal.onabort === 'function') {
53+
signal.onabort(event);
54+
}
55+
signal.dispatchEvent(event);
56+
}
57+
58+
class AbortController {
59+
#signal = new AbortSignal();
60+
61+
constructor() {
62+
emitExperimentalWarning('AbortController');
63+
}
64+
65+
get signal() { return this.#signal; }
66+
abort() { abortSignal(this.#signal); }
67+
68+
[customInspectSymbol](depth, options) {
69+
return customInspect(this, {
70+
signal: this.signal
71+
}, depth, options);
72+
}
73+
}
74+
75+
Object.defineProperties(AbortController.prototype, {
76+
signal: { enumerable: true },
77+
abort: { enumerable: true }
78+
});
79+
80+
module.exports = {
81+
AbortController,
82+
AbortSignal,
83+
};

lib/internal/bootstrap/pre_execution.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
NumberParseInt,
55
ObjectDefineProperty,
6+
ObjectDefineProperties,
67
SafeMap,
78
SafeWeakMap,
89
StringPrototypeStartsWith,
@@ -58,6 +59,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
5859
// (including preload modules).
5960
initializeClusterIPC();
6061

62+
initializeAbortController();
6163
initializeDeprecations();
6264
initializeWASI();
6365
initializeCJSLoader();
@@ -312,6 +314,30 @@ function initializeDeprecations() {
312314
});
313315
}
314316

317+
function initializeAbortController() {
318+
const abortController = getOptionValue('--experimental-abortcontroller');
319+
if (abortController) {
320+
const {
321+
AbortController,
322+
AbortSignal
323+
} = require('internal/abort_controller');
324+
ObjectDefineProperties(global, {
325+
AbortController: {
326+
writable: true,
327+
enumerable: false,
328+
configurable: true,
329+
value: AbortController
330+
},
331+
AbortSignal: {
332+
writable: true,
333+
enumerable: false,
334+
configurable: true,
335+
value: AbortSignal
336+
}
337+
});
338+
}
339+
}
340+
315341
function setupChildProcessIpcChannel() {
316342
if (process.env.NODE_CHANNEL_FD) {
317343
const assert = require('internal/assert');
@@ -448,6 +474,7 @@ module.exports = {
448474
setupWarningHandler,
449475
setupDebugEnv,
450476
prepareMainThreadExecution,
477+
initializeAbortController,
451478
initializeDeprecations,
452479
initializeESMLoader,
453480
initializeFrozenIntrinsics,

lib/internal/main/worker_thread.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
setupInspectorHooks,
1414
setupWarningHandler,
1515
setupDebugEnv,
16+
initializeAbortController,
1617
initializeDeprecations,
1718
initializeWASI,
1819
initializeCJSLoader,
@@ -112,6 +113,7 @@ port.on('message', (message) => {
112113
if (manifestSrc) {
113114
require('internal/process/policy').setup(manifestSrc, manifestURL);
114115
}
116+
initializeAbortController();
115117
initializeDeprecations();
116118
initializeWASI();
117119
initializeCJSLoader();

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
'lib/wasi.js',
9696
'lib/worker_threads.js',
9797
'lib/zlib.js',
98+
'lib/internal/abort_controller.js',
9899
'lib/internal/assert.js',
99100
'lib/internal/assert/assertion_error.js',
100101
'lib/internal/assert/calltracker.js',

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
281281
"experimental Source Map V3 support",
282282
&EnvironmentOptions::enable_source_maps,
283283
kAllowedInEnvironment);
284+
AddOption("--experimental-abortcontroller",
285+
"experimental AbortController support",
286+
&EnvironmentOptions::experimental_abortcontroller,
287+
kAllowedInEnvironment);
284288
AddOption("--experimental-json-modules",
285289
"experimental JSON interop support for the ES Module loader",
286290
&EnvironmentOptions::experimental_json_modules,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class EnvironmentOptions : public Options {
102102
bool abort_on_uncaught_exception = false;
103103
std::vector<std::string> conditions;
104104
bool enable_source_maps = false;
105+
bool experimental_abortcontroller = false;
105106
bool experimental_json_modules = false;
106107
bool experimental_modules = false;
107108
std::string experimental_specifier_resolution;

test/parallel/test-abortcontroller.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Flags: --no-warnings --experimental-abortcontroller
2+
'use strict';
3+
4+
const common = require('../common');
5+
6+
const { ok, strictEqual } = require('assert');
7+
8+
{
9+
const ac = new AbortController();
10+
ok(ac.signal);
11+
ac.signal.onabort = common.mustCall((event) => {
12+
ok(event);
13+
strictEqual(event.type, 'abort');
14+
});
15+
ac.signal.addEventListener('abort', common.mustCall((event) => {
16+
ok(event);
17+
strictEqual(event.type, 'abort');
18+
}), { once: true });
19+
ac.abort();
20+
ac.abort();
21+
ok(ac.signal.aborted);
22+
}

tools/doc/type-parser.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const customTypesMap = {
2626

2727
'this': `${jsDocPrefix}Reference/Operators/this`,
2828

29+
'AbortController': 'globals.html#globals_class_abortcontroller',
30+
'AbortSignal':
31+
'globals.html#globals_class_abortsignal_extends_eventtarget',
32+
2933
'ArrayBufferView':
3034
'https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView',
3135

0 commit comments

Comments
 (0)