Skip to content

events: add addDisposableListener method to EventEmitter #58453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jasnell
Copy link
Member

@jasnell jasnell commented May 25, 2025

This adds new use(...) and useOnce(...) methods addDisposableListener method to EventEmitter that returns a disposable object that will unregister the event listeners when disposed, along with two changes that use the new apis to demonstrate how it can be used to simplify some cleanup.

const ee = new EventEmitter();
{
  using ds = new DisposableStack();
  ds.use(ee.addDisposableListener('foo', () => {});
  ds.use(ee.addDisposableListener('bar', () => {});
  if (something) ds.use(ee.addDisposableListener('baz', () => {});
}
// The event listeners will all be removed automatically when ds is disposed.
// We don't need to separately remember to call removeListener/off to cleanup the event listeners

@jasnell jasnell requested review from mcollina, addaleax and anonrig May 25, 2025 19:01
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/streams
  • @nodejs/test_runner

@nodejs-github-bot nodejs-github-bot added events Issues and PRs related to the events subsystem / EventEmitter. needs-ci PRs that need a full CI run. stream Issues and PRs related to the stream subsystem. test_runner Issues and PRs related to the test runner subsystem. labels May 25, 2025
@jasnell
Copy link
Member Author

jasnell commented May 31, 2025

Relevant to advancing on this: #58526

@mcollina
Copy link
Member

I'm ok with the addition. I'm a bit worried about the potential change in behavior, as I didn't have time to explore yet if there is a change in the order of operations.

@jasnell
Copy link
Member Author

jasnell commented May 31, 2025

What change in behavior are you concerned about? This should not change any existing behavior of the EventEmitter

@jasnell jasnell force-pushed the jasnell/eventemitter-using branch from 718fe84 to 2657ecb Compare June 1, 2025 17:52
@jasnell jasnell changed the title events: Add use/useOnce methods to EventEmitter events: add addDisposableListener method to EventEmitter Jun 1, 2025
@jasnell
Copy link
Member Author

jasnell commented Jun 1, 2025

I've updated the implementation to rename the use method to a single addDisposableListener method with a once option. It returns a dispose function that has the Symbol.dispose property attached to call itself. PTAL

@jasnell jasnell requested a review from LiviaMedeiros June 1, 2025 17:55
Copy link
Member

@LiviaMedeiros LiviaMedeiros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with removal of obsolete no-undefs and assuming consensus on this in #58526.
Perhaps using dispose === dispose[Symbol.dispose] can be made into recommended pattern (whenever we don't have more meaningful return values) in the ERM guideline.

@benjamingr
Copy link
Member

Is there any way we can avoid adding yet another way to register events? i.e. can this be an option and not a method?

I think this was blocked previously in AbortSignal due to a V8 optimization that happened since.

@jasnell
Copy link
Member Author

jasnell commented Jun 1, 2025

Is there any way we can avoid adding yet another way to register events? i.e. can this be an option and not a method?

Not without adding too much complexity. As it is now, addListener(...) returns the EventEmitter itself. Adding an option would mean making the return value polymorphic which is far worse than adding a new separate API. See the discussion around #58526

@Renegade334
Copy link
Contributor

Renegade334 commented Jun 2, 2025

Regarding the single-use disposable that's just a disposer, with no associated object data: is

function dispose() { ... }
dispose[SymbolDispose] = dispose
return dispose

OK, or should we be keeping things consistent with the draft guidance for disposers in general?

function dispose() { ... }
return {
  dispose,
  [SymbolDispose]: dispose,
  // or
  [SymbolDispose]() { dispose() },
}

My vote would be for the latter – it would be an inconsistent pattern for disposables to sometimes be directly callable, and other times not.

@jasnell jasnell force-pushed the jasnell/eventemitter-using branch from 509efeb to 271fb76 Compare June 7, 2025 14:04
@jasnell jasnell marked this pull request as ready for review June 7, 2025 14:05
@jasnell jasnell requested review from benjamingr and Renegade334 June 7, 2025 14:05
@jasnell jasnell added semver-minor PRs that contain new features and should be released in the next minor version. experimental Issues and PRs related to experimental features. labels Jun 7, 2025
@nodejs-github-bot

This comment was marked as outdated.

@jasnell jasnell force-pushed the jasnell/eventemitter-using branch from 271fb76 to f4ca9ae Compare June 8, 2025 00:55
@nodejs-github-bot
Copy link
Collaborator

nodejs-github-bot commented Jun 8, 2025

Copy link

codecov bot commented Jun 8, 2025

Codecov Report

Attention: Patch coverage is 98.66667% with 1 line in your changes missing coverage. Please review.

Project coverage is 90.14%. Comparing base (3c351c2) to head (f4ca9ae).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/streams/end-of-stream.js 94.11% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #58453      +/-   ##
==========================================
+ Coverage   90.13%   90.14%   +0.01%     
==========================================
  Files         636      636              
  Lines      187891   187932      +41     
  Branches    36878    36883       +5     
==========================================
+ Hits       169348   169408      +60     
- Misses      11289    11290       +1     
+ Partials     7254     7234      -20     
Files with missing lines Coverage Δ
lib/events.js 99.60% <100.00%> (+0.01%) ⬆️
lib/internal/test_runner/harness.js 93.41% <100.00%> (+0.06%) ⬆️
lib/internal/streams/end-of-stream.js 97.23% <94.11%> (-0.05%) ⬇️

... and 34 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell jasnell added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Jun 8, 2025
Comment on lines +520 to +522
* Returns: {Object} An object with a dispose method that will remove the listener.
The function will also have a `Symbol.dispose` method so the function can
be used with the `using` keyword.
Copy link
Member

@legendecas legendecas Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning an anonymous object in the API makes it hard to discover, like searching API interfaces with [Symbol.dispose] method. Additionally, the properties of the anynomous objects are not as clear as a named types. I'd prefer a named interface for the event listener disposable type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. events Issues and PRs related to the events subsystem / EventEmitter. experimental Issues and PRs related to experimental features. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version. stream Issues and PRs related to the stream subsystem. test_runner Issues and PRs related to the test runner subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants