Skip to content

Disposable AbortController #1405

Open
Open

Description

@bakkot

What problem are you trying to solve?

Now that explicit resource management is shipping, it would be very useful if there were a way to get an AbortController which aborted when disposed. That would let you write something like

using controller = new AbortController.AutoAbort();
let pages = await Promise.all(urls.map(url => fetch(url, { signal: controller.signal }));
// automatically cancels outstanding requests if any request fails

without needing an explicit try/finally. See some further discussion here.

What solutions exist today?

You can use a try-finally:

let controller = new AbortController;
let pages;
try {
  pages = await Promise.all(urls.map(url => fetch(url, { signal: controller.signal }));
} finally {
  controller.abort();
}

or make a DisposableStack:

using stack = new DisposableStack();
const { signal } = stack.adopt(new AbortController(), (controller) => controller.abort());
let pages = await Promise.all(urls.map(url => fetch(url, { signal }));

but those are both pretty awkward.

How would you solve it?

I would add a new kind of AbortController which implements the disposable protocol and which aborts when disposed.

In principle this is doable with an option to the constructor, but that means the return type depends on the arguments to the constructor, which is generally considered awkward.

Another option is to just make all AbortControllers disposable in this way. @rbuckton has argued against this because he wants some way to mark an AbortController as "closed but not aborted" and remove all callbacks, and wants this to be the default disposing behavior. I'm fine with that I guess.

A third option is to have the AbortController be disposable and have an option to control whether disposing aborts or merely "closes" the AbortController as in the previous paragraph.

(Sorry for using the wrong issue template initially.)

Activity

  1. justinfagnani commented on Sep 30, 2025

    @justinfagnani

    Could AbortController just be made disposable? It would only change behavior in the case where an AbortController is declared with using, and since AbortController is not now disposable, that seems rather unlikely to be happening in the wild.

  2. bakkot commented on Sep 30, 2025

    @bakkot
    Author

    As it says in the OP, @rbuckton has argued against this because he wants some way to mark an AbortController as "closed but not aborted" and remove all callbacks, and wants this to be the default disposing behavior.

    But yes, it could be done that way. That said, I expect a lot of tooling to (eventually) highlight when a disposable value is not used with using, which means it may be undesirable for APIs which aren't primarily used this way to be disposable.

    Though, maybe it should primarily be used with using? A majority of my AbortControllers (although far from all) don't live beyond their declaring scope.

    Anyway, I'm OK either way.


    Edit, re:

    It would only change behavior in the case where an AbortController is declared with using, and since AbortController is not now disposable, that seems rather unlikely to be happening in the wild

    It's actually an error to declare a non-disposable resource with using. Making an error into a non-error is almost always web compat, so it should be fine.

  3. justinfagnani commented on Sep 30, 2025

    @justinfagnani

    As it says in the OP, @rbuckton has argued against this because he wants some way to mark an AbortController as "closed but not aborted" and remove all callbacks, and wants this to be the default disposing behavior.

    Oops, sorry, I was reading too fast. Thanks for the context. I can see why "release resources, but don't abort" might be a good default dispose behavior.

    But yes, it could be done that way. That said, I expect a lot of tooling to (eventually) highlight when a disposable value is not used with using, which means it may be undesirable for APIs which aren't primarily used this way to be disposable.

    Though, maybe it should primarily be used with using? A majority of my AbortControllers (although far from all) don't live beyond their declaring scope.

    I personally most often AbortControllers stored in a class instance field or pushed into some collection, but would be fine with having to tell tooling about that, or having more sophisticated heuristics that detect that. Encouraging code to automatically release controller resources seems important enough.

  4. keithamus commented on Sep 30, 2025

    @keithamus
    Member

    Sounds like there is perhaps a request for two levels of disposability then?

    1. To consider an AbortController "closed" or "past the point of no return" whereby it frees up the associated callbacks because it knows it won't call them, and then also presumably throws if abort() is called.
    2. To consider an AbortController "aborted" where it does the usual abort steps.

    I can acknowledge the desire for 1 but it seems problematic for event cases on long lived DOM objects. If attach a bunch of event listeners and your only way to free them is to abort, accidentally closing your AbortController is effectively like forcing a leak on the event listeners.

    I'd perhaps make the case that using AbortController() should follow case 2, and for Ron's case of closing an abort controller, if we go that route, we can add a using AbortController().ButItClosesAndDoesntAbortSoBeCareful().

  5. bakkot commented on Sep 30, 2025

    @bakkot
    Author

    I'm only personally advocating for the second case. I am neutral between making this the default behavior of AbortController vs adding a new subclass.

    Getting a .abort when you don't expect it is the sort of thing which tends to be noticeable, whereas failing to .abort is often not noticeable (you just needlessly consume additional resources), so I think if we do make AbortController itself disposable then having the default behavior be to .abort will be less frustrating than having it be to close-without-abort. If someday the close-without-abort functionality is added to AbortController we can at that time add a separate { disposal: "close-without-abort" } option to the constructor to opt in to that behavior.

    Also, subclass is probably a heavier lift, so if there's implementer interest and I find time to work on this I would go the route of making AbortController disposable and having that .abort.

    I will also note that having AbortController be disposable and having that do a .abort is, if you squint, kind of like structured concurrency: assuming you pass its signal to every async operation launched from within the scope where you declare the controller, then exiting the scope where you declare the controller will also cancel those operations.

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 enhancementsneeds implementer interestMoving the issue forward requires implementers to express interesttopic: abortingAbortController and AbortSignal

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions