From 7efde1cc478b9787aa02f425b875654aa3e3fa3c Mon Sep 17 00:00:00 2001 From: atlowChemi Date: Thu, 29 Jun 2023 13:19:24 +0300 Subject: [PATCH] events: allow safely adding listener to abortSignal --- doc/api/events.md | 59 +++++++++++++++++++ lib/events.js | 31 ++++++++++ .../test-events-add-abort-listener.mjs | 55 +++++++++++++++++ tools/doc/type-parser.mjs | 1 + 4 files changed, 146 insertions(+) create mode 100644 test/parallel/test-events-add-abort-listener.mjs diff --git a/doc/api/events.md b/doc/api/events.md index ffeabe2fb53860..c4e58442991364 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -1801,6 +1801,64 @@ const emitter = new EventEmitter(); setMaxListeners(5, target, emitter); ``` +## `events.addAbortListener(signal, resource)` + + + +> Stability: 1 - Experimental + +* `signal` {AbortSignal} +* `listener` {Function|EventListener} +* Returns: {Disposable} that removes the `abort` listener. + +Listens once to the `abort` event on the provided `signal`. + +Listening to the `abort` event on abort signals is unsafe and may +lead to resource leaks since another third party with the signal can +call [`e.stopImmediatePropagation()`][]. Unfortunately Node.js cannot change +this since it would violate the web standard. Additionally, the original +API makes it easy to forget to remove listeners. + +This API allows safely using `AbortSignal`s in Node.js APIs by solving these +two issues by listening to the event such that `stopImmediatePropagation` does +not prevent the listener from running. + +Returns a disposable so that it may be unsubscribed from more easily. + +```cjs +const { addAbortListener } = require('node:events'); + +function example(signal) { + let disposable; + try { + signal.addEventListener('abort', (e) => e.stopImmediatePropagation()); + disposable = addAbortListener(signal, (e) => { + // Do something when signal is aborted. + }); + } finally { + disposable?.[Symbol.dispose](); + } +} +``` + +```mjs +import { addAbortListener } from 'node:events'; + +function example(signal) { + let disposable; + try { + signal.addEventListener('abort', (e) => e.stopImmediatePropagation()); + disposable = addAbortListener(signal, (e) => { + // Do something when signal is aborted. + }); + } finally { + disposable?.[Symbol.dispose](); + } +} +``` + ## Class: `events.EventEmitterAsyncResource extends EventEmitter`