Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
提交
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,10 @@ added:
- v20.5.0
- v18.18.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65640
description: When the signal is already aborted, the listener now receives an
`abort` event and disposing cancels the pending call.
- version:
- v24.0.0
- v22.16.0
Expand All @@ -1908,6 +1912,9 @@ not prevent the listener from running.

Returns a disposable so that it may be unsubscribed from more easily.

If `signal` is already aborted, the listener is called with an `abort` event in a
microtask. Disposing before that microtask runs cancels the call.

```cjs
const { addAbortListener } = require('node:events');

Expand Down
2 changes: 2 additions & 0 deletions lib/internal/event_target.js
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,8 @@ module.exports = {
kTrustEvent,
kRemoveListener,
kEvents,
kIsBeingDispatched,
kTarget,
kWeakHandler,
kResistStopPropagation,
isEventTarget,
Expand Down
16 changes: 15 additions & 1 deletion lib/internal/events/abort_listener.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
let queueMicrotask;
let kResistStopPropagation;
let abortListenerOptions;
let eventTarget;

/**
* @param {AbortSignal} signal
Expand All @@ -33,7 +34,20 @@ function addAbortListener(signal, listener) {
let removeEventListener;
if (signal.aborted) {
queueMicrotask ??= require('internal/process/task_queues').queueMicrotask;
queueMicrotask(() => listener());
eventTarget ??= require('internal/event_target');
let disposed = false;
queueMicrotask(() => {
if (disposed) return;
const { Event, kIsBeingDispatched, kTarget, kTrustEvent } = eventTarget;
const event = new Event('abort', { [kTrustEvent]: true });
event[kTarget] = signal;
event[kIsBeingDispatched] = true;
listener(event);
event[kIsBeingDispatched] = false;
});
removeEventListener = () => {
disposed = true;
};
} else {
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
abortListenerOptions ??= ObjectFreeze({ __proto__: null, once: true, [kResistStopPropagation]: true });
Expand Down
19 changes: 19 additions & 0 deletions test/parallel/test-events-add-abort-listener.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ describe('events.addAbortListener', () => {
assert.strictEqual(typeof disposable[Symbol.dispose], 'function');
});

it('should pass an abort event to the listener for aborted runners', async () => {
const signal = AbortSignal.abort();
const { promise, resolve } = Promise.withResolvers();
events.addAbortListener(signal, (e) => resolve([e, e?.currentTarget]));

const [event, currentTarget] = await promise;
assert.strictEqual(event.type, 'abort');
assert.strictEqual(event.target, signal);
assert.strictEqual(event.isTrusted, true);
assert.strictEqual(currentTarget, signal);
});

it('should not execute the listener for aborted runners when disposed', async () => {
const disposable = events.addAbortListener(AbortSignal.abort(), common.mustNotCall());
disposable[Symbol.dispose]();

await new Promise(setImmediate);
});

it('should execute the listener even when event propagation stopped', () => {
const controller = new AbortController();
const { signal } = controller;
Expand Down
Loading