Skip to content

Support AbortSignals in addEventListeners options to unsubscribe from events? #911

Closed

Description

@benjamingr

Since Node added (experimental) support for AbortController, we have also added support for out event utility functions (that already work with EventTargets) for cancellation:

import { once, EventEmitter } from 'events';
import { setTimeout } from 'timers/promises;

const et = new EventTarget();
const ee = new EventEmitter();
const ac = new AbortController();

setTimeout(100).then(() => ac.abort());

let { signal } = ac;
// run with experimental tla flag or wrap in an iife
await once(ee, 'foo', { signal }); // cancel waiting for the event, removes the listener
await once(et, 'foo', { signal }); // same thing, with event target, removes the listener
for await(const item of on(ee, 'foo' , { signal } )) { // same thing, but with async iterator variant
}

It would be useful to "upstream" this behaviour to the DOM specification as it would be useful to have in browsers:

// Does not yet work, suggested:
const ac = new AbortController();
let { signal } = ac;
const et = new EventTarget();
et.addEventListener('foo', (e) => {
  
}, { signal } );
ac.abort(); // removes the listener from the event target, same as et.removeEventListener('foo', thatFunctionReference)

It's useful to use the web's cancellation platform (AbortController) to cancel listening to an event. This is also ergonomic for frameworks/libraries that set up a lot of event listeners and could unsubscribe from all of them in one go (through the controller).

Was this suggested at some point in the past? (I couldn't find it) Is this a good idea? A bad idea?

cc @domenic @annevk

Activity

  1. annevk commented on Oct 29, 2020

    @annevk
    Member

    I see, so this would remove event listeners upon the signal that is associated with them being aborted? I haven't seen suggestions for this.

  2. benjamingr commented on Oct 29, 2020

    @benjamingr
    MemberAuthor

    I see, so this would remove event listeners upon the signal that is associated with them being aborted? I haven't seen suggestions for this.

    Yes, basically it would be a way to make working with EventTargets more consistent with other DOM APIs.

    For example: the person who asked me for this said "I need to make a few requests and subscribe to a few events when the user navigates to a certain page in my SPA. I am already using an AbortController to abort all the pending requests if the user navigates away - it would be very useful to also unsubscribe from all events the same way essentially having one way to 'cleanup' "

  3. domenic commented on Oct 29, 2020

    @domenic
    Member

    This was suggested in the context of .on() and observables in #544, but extending addEventListener seems like a nice incremental approach.

    I'm quite supportive of this change. @mfreed7 do you think this would be something the Chromium DOM team could plan to implement? I know it's always a bit hard to make the case for spending time on minor ergonomic improvements, but see above for some web developer testimony on why this would be appreciated.

  4. mfreed7 commented on Nov 6, 2020

    @mfreed7
    Contributor

    I'm quite supportive of this change. @mfreed7 do you think this would be something the Chromium DOM team could plan to implement? I know it's always a bit hard to make the case for spending time on minor ergonomic improvements, but see above for some web developer testimony on why this would be appreciated.

    This definitely seems like a nice addition to the platform - I can see the use case for cleaning everything up in one way. I can't guarantee timing, but we could probably take a look at prototyping this, if someone else can handle the spec work?

  5. mfreed7 commented on Nov 10, 2020

    @mfreed7
    Contributor
  6. josepharhar commented on Nov 10, 2020

    @josepharhar
    Contributor
  7. benjamingr commented on Nov 10, 2020

    @benjamingr
    MemberAuthor

    That's awesome @josepharhar thanks!

    I've started working on WPTs here: web-platform-tests/wpt#26472 I haven't run them or tested them fully yet but feel free to use the cases.

  8. 11 remaining items

  9. smaug---- commented on Nov 24, 2020

    @smaug----
    Contributor
  10. benjamingr commented on Nov 25, 2020

    @benjamingr
    MemberAuthor

    @annevk can you please explain what the next steps in the process are?

    We have:

    To be extra clear - I am in no rush, thankful that I get to work on this and am doing this for fun and to improve the web platform. I just want to understand what the next steps are.

    Edit: I've also gone ahead and made a PR to Node.js to add this at https://github.com/nodejs/node/pull/36258/files

  11. annevk commented on Nov 30, 2020

    @annevk
    Member

    Hey @benjamingr, everything looks pretty splendid! I left some comments on the PR and test PR, but nothing major. I think you covered all the bullet points from https://whatwg.org/working-mode#changes.

  12. added a commit that references this issue on Dec 3, 2020
    83037a1
  13. josepharhar commented on Jan 26, 2021

    @josepharhar
    Contributor

    I just filed an intent to ship for chrome so I can enable this feature by default.
    This should probably get documented on MDN, right?

  14. added
    impacts documentationUsed by documentation communities, such as MDN, to track changes that impact documentation
    on Jan 26, 2021
  15. annevk commented on Jan 26, 2021

    @annevk
    Member

    @whatwg/documentation it would be good to put this on MDN.

  16. josepharhar commented on Jan 28, 2021

    @josepharhar
    Contributor

    This will be enabled by default in chrome 90

  17. jimmywarting commented on Feb 6, 2021

    @jimmywarting
  18. sideshowbarker commented on Feb 28, 2021

    @sideshowbarker
    Member

    FYI: I raised mdn/content#2759 with a patch that documents this feature in MDN. Review welcome (from anybody interested).

  19. caub commented on Apr 29, 2021

    @caub

    Oh great feature, so something like that below can be rewritten with AbortController

    button.addEventListener('click', () => {
      // ...
    
      function closeByClick() {
        // ... do stuff
        closeButton.removeEventListener('click', closeByClick);
        window.removeEventListener('keydown', closeByEscape);
      }
      function closeByEscape(e) {
        if (e.key !== 'Escape') return;
        // ... do stuff
        closeButton.removeEventListener('click', closeByClick);
        window.removeEventListener('keydown', closeByEscape);
      }
    
      closeButton.addEventListener('click', closeByClick);
      window.addEventListener('keydown', closeByEscape);
    });

    like this:

    button.addEventListener('click', () => {
      // ...
      const controller = new AbortController();
    
      closeButton.addEventListener('click', () => {
        // ... do stuff
        controller.abort();
      }, { signal: controller.signal });
    
      window.addEventListener('keydown', e => {
        if (e.key !== 'Escape') return;
        // ... do stuff
        controller.abort();
      }, { signal: controller.signal });
    });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    addition/proposalNew features or enhancementsimpacts documentationUsed by documentation communities, such as MDN, to track changes that impact documentationneeds implementer interestMoving the issue forward requires implementers to express interesttopic: abortingAbortController and AbortSignaltopic: events

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions