From a316808136c8c235ef0dde2fc377f40946dcb686 Mon Sep 17 00:00:00 2001 From: Chemi Atlow Date: Thu, 6 Jul 2023 01:04:23 +0300 Subject: [PATCH] events: allow safely adding listener to abortSignal PR-URL: https://github.com/nodejs/node/pull/48596 Reviewed-By: Matteo Collina Reviewed-By: Moshe Atlow Reviewed-By: Benjamin Gruenbaum Reviewed-By: Robert Nagy --- 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 07fc7a3cbeacd8..212de915cb4a52 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -1799,6 +1799,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`