Repository navigation
Disposable AbortController #1405
Description
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 failswithout 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
justinfagnani commentedon Sep 30, 2025
Could
AbortControllerjust be made disposable? It would only change behavior in the case where an AbortController is declared withusing, and since AbortController is not now disposable, that seems rather unlikely to be happening in the wild.bakkot commentedon Sep 30, 2025
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'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.justinfagnani commentedon Sep 30, 2025
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.
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.
keithamus commentedon Sep 30, 2025
Sounds like there is perhaps a request for two levels of disposability then?
abort()is called.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 ausing AbortController().ButItClosesAndDoesntAbortSoBeCareful().bakkot commentedon Sep 30, 2025
I'm only personally advocating for the second case. I am neutral between making this the default behavior of
AbortControllervs adding a new subclass.Getting a
.abortwhen you don't expect it is the sort of thing which tends to be noticeable, whereas failing to.abortis often not noticeable (you just needlessly consume additional resources), so I think if we do makeAbortControlleritself disposable then having the default behavior be to.abortwill be less frustrating than having it be to close-without-abort. If someday the close-without-abort functionality is added toAbortControllerwe 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
AbortControllerdisposable and having that.abort.I will also note that having
AbortControllerbe disposable and having that do a.abortis, 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.