Skip to content

Thrown AbortErrors are not DOMExceptions #40692

Description

@kanongil

Version

v17.0.1

Platform

any

Subsystem

No response

What steps will reproduce the bug?

import { readFile } from 'fs/promises';

const ref = new DOMException('The operation was aborted', 'AbortError');
console.log('REF INSTANCE', ref instanceof DOMException);
console.log('REF OBJECT', ref);

const ab = new AbortController();
ab.abort();

try {
    await readFile('does_not_matter', { signal: ab.signal });
}
catch (err) {
    console.log('ERR INSTANCE', err instanceof DOMException);
    console.log('ERR OBJECT', err);
}

How often does it reproduce? Is there a required condition?

100%, including for all other native APIs that supports signals.

What is the expected behavior?

From the docs:

If a request is aborted the promise returned is rejected with an AbortError

There is no mention that this is a non-standard new DOMException(message, 'AbortError') error. As such, I expect 'REF' and 'ERR' logged objects to be the same (except the stack).

What do you see instead?

REF INSTANCE true
REF OBJECT DOMException [AbortError]: The operation was aborted
    at file://<snip>/fail.mjs:3:13
    at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)
ERR INSTANCE false
ERR OBJECT AbortError: The operation was aborted
    at checkAborted (node:internal/fs/promises:373:11)
    at readFile (node:internal/fs/promises:845:3)
    at file://<snip>/fail.mjs:11:11
    at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12) {
  code: 'ABORT_ERR'
}

Additional information

This is only an issue in node 17+, since previous versions did not have a DOMException global.

Activity

  1. benjamingr commented on Nov 1, 2021

    @benjamingr
    Member

    Hey, this is by design, you can see the context and meeting in #36084. Node.js does throw DOMExceptions for DOM APIs. For its own APIs Node.js prefers a simpler AbortError with the same .name property (which is the preferred way to check for AbortErrors according to the DOM spec). The reason for this is that Node.js vendors some of its modules (like readable-stream) and having to ship DOMException with them would be hard.

  2. kanongil commented on Nov 2, 2021

    @kanongil
    ContributorAuthor

    @benjamingr That design does not appear to have been revised when node added support for native DOMException. This is causing extra confusion, and the root of this issue.

    Essentially, this decision puts the onus on API consumers to handle the strange complexity, and on API documenters to clearly convey this, just so node can potentially simpler vendor readable-stream (which has not actually happened due to other complexities).

    Given that there is no obvious central place to document this complexity, any API that uses a signal must clearly communicate this, which is currently failing.

    I hope you will take a chance to reconsider this. You are prioritizing the efforts of a few core developers against the documentation team, and the entire developer base.

  3. benjamingr commented on Nov 2, 2021

    @benjamingr
    Member

    @benjamingr That design does not appear to have been revised when node added support for native DOMException. This is causing extra confusion, and the root of this issue.

    Node has added DOMException before AbortSignal - the decision was to move away from DOMExceptions for non DOM APIs using AbortSignal and stick to .name (the reasoning is in the thread I linked to and in the meeting minutes).

    (Node does vendor readable-stream as the readable-stream package on NPM btw - but it hasn't been updated)

    Given that there is no obvious central place to document this complexity, any API that uses a signal must clearly communicate this, which is currently failing.

    Would it help if the documentations clarified the AbortError interface we use like we do for AbortSignal and friends?

    I hope you will take a chance to reconsider this. You are prioritizing the efforts of a few core developers against the documentation team, and the entire developer base.

    I don't think that's an entirely fair or an accurate representation of how things transpired:

    • AbortController/AbortSignal was added to the platform to enable cancellation scenarios.
    • We iterated on them and added them to more APIS while soliciting responses from the community, library authors, WHATWG folks and other subject-matter-experts.
    • When soliciting feedback from library vendors and core - the concern about DOMException being complicated and different was raised. There were no objections to changing it - you are in fact the first person to bring up this complaint in the ±year we've shipped this.
    • The process was open and in the public, anyone from the community was welcome to participate and many were invited specifically so we collect user feedback. We've had a meeting about this in the summit (online, also open to everyone) and several other discussions.
    • This decision was made considering the people who built AbortController/AbortSignal/AbortError in the DOM in the first place, people who worked on other AbortSignal APIs, the community, library authors and more.
    • Things quieted down pretty quickly and we're getting positive feedback.

    Also I want to point out that the decision isn't final, we can revisit anything here. We did not receive any pushback about this change from the documentation team nor developer base. We are happily willing to listen to feedback.

    All of this is relatively new and improvement opportunities (in docs, APIs, debugging experience and more) are welcome. Contributions are welcome and Node.js in general and this area in particular is eager to receive them.

  4. kanongil commented on Nov 2, 2021

    @kanongil
    ContributorAuthor

    Thanks for the thorough response, and good to know that there is still room for iterating the approach.

    DOMException has only just been made public with node v17, so has only been there for a few weeks for API consumers. It was merged August 6'th in e4b1fb5, and I don't see any considerations on how this impacts AbortError in any of the relevant PRs. Issue #39098 explicitly mentions the ability to do instanceof checks. #39176 can hardly have been a consideration almost a year ago in #36084.

    Also, most feedback you will have gotten so far is likely not from the wider dev community, as many still support node v12, and are only just starting to look into using the features v14+ provides due to v12 EOL coming up.

    FYI, I'm very exited about this new feature, which is why I want to see it done right. Here I think unifying on new DOMException(message, 'AbortError') for node native and modules targeting v17+ will make it simpler to use. Both as providers (those throwing) and consumers (those passing signal / calling abort()) that are likely to have to ignore the subsequent rejections. Besides making it simpler to use the feature, it also enables compatibility with browser runtimes.

    This still leaves module that target v14/v16 and v17+ "in the dust", having to deal with extra complexity to handle two different error implementations. However, they are no worse of than the current situation, and will eventually be able to drop the legacy codepaths.

    Given that there is no obvious central place to document this complexity, any API that uses a signal must clearly communicate this, which is currently failing.

    Would it help if the documentations clarified the AbortError interface we use like we do for AbortSignal and friends?

    Yes – this would be simpler to document if the internal AbortError was public (as discussed in #38361).

  5. benjamingr commented on Nov 2, 2021

    @benjamingr
    Member

    Thanks for the thorough response, and good to know that there is still room for iterating the approach.

    Absolutely.

    DOMException has only just been made public with node v17, so has only been there for a few weeks for API consumers. It was merged August 6'th in e4b1fb5, and I don't see any considerations on how this impacts AbortError in any of the relevant PRs. Issue #39098 explicitly mentions the ability to do instanceof checks.

    It's true that DOMException has been made public in Node.js 17 but it has been in core for a few years now (since things like URL raise it). Note that a public DOMException is even worse for stuff like readable-stream that exists for compatibility across versions since people may expect instanceof checks to work (and they won't) - neither will instanceof checks work across vm contexts and other "realms".

    In general it makes perfect sense to expose DOMException as well as AbortError but people should never rely on instanceof checks for errors. That's why there were proposals for stuff like Error.isError.

    If you read the WHATWG fetch specification carefully (I am happy to dig up the discussions where we initially met to discuss AbortController) - you will see it makes no requirements on raising AbortError. In fact when I added an API myself (signal in addEventListener) there were no errors involved.

    DOM APIs are encouraged but not required to reject with an AbortError DOMException - non DOM APIs have no such requirements. If you look at the spec example it encourages checking .name rather than instanceof (for the aforementioned reasons) that is quite intentional.

    #39176 can hardly have been a consideration almost a year ago in #36084.

    I actually recall seeing (but not having time to review) that and trusting reviewers since I think it's overall a good idea.

    Also, most feedback you will have gotten so far is likely not from the wider dev community, as many still support node v12, and are only just starting to look into using the features v14+ provides due to v12 EOL coming up.

    This was less "users approaching us" and more "us approaching users and asking what they think" and "us talking to people who have experimented with userland AbortSignals (like SDK authors) and asking them what feedback their users are giving. Gathering user and library maintainer feedback is always a big challenge in Node.js and always something we could use more of.

    FYI, I'm very exited about this new feature, which is why I want to see it done right. Here I think unifying on new DOMException(message, 'AbortError') for node native and modules targeting v17+ will make it simpler to use. Both as providers (those throwing) and consumers (those passing signal / calling abort()) that are likely to have to ignore the subsequent rejections. Besides making it simpler to use the feature, it also enables compatibility with browser runtimes.

    Note that we switched from DOMException - I think this is where it was added after the meeting and consensus.

    Unless the concerns raised by @mcollina regarding this being hard for readable stream and other parts were addressed in his perspective - I don't see us switching back to DOMExceptions (which wouldn't break a lot of code probably since people are supposed to check .name but it might).

    Yes – this would be simpler to document if the internal AbortError was public (as discussed in #38361).

    Is that something you'd be interested in contributing? I know we've talked about this in the past but I am honestly not sure what it's blocked on and I am happy for us to start with a simpler flagged errors module and add more codes/errors.

    How can I help you with this? (I can review, ping the TSC to discuss the module inclusion, try to get more people to look at this etc).

  6. mcollina commented on Nov 2, 2021

    @mcollina
    SponsorMember

    I don't have much time right now for joining a conversation with this depth. From a quick skim @benjamingr summarizes this correctly. My position has not changed on this matter.

  7. kanongil commented on Nov 2, 2021

    @kanongil
    ContributorAuthor

    We agree that it makes sense for user code to distinguish AbortError from regular errors, eg. to avoid logging it, right? Thus it makes sense that the error included in the rejection (when triggered) is clearly identifiable. As you say, this is currently done through checking the .name property, but it relies on APIs to reject using properly crafted errors.

    While the node APIs can be aligned on what is thrown, it is anyones guess what third-party modules will throw. And since those modules might forward the signal to submodules, the same API call could be able to reject using two or more completely different abort errors, depending on when it is aborted. Ie. you can't even reliably test against how abort errors are emitted.

    For a different take on this, node could expose helpers like this to enable tests for .aborted and throwing using a standardized AbortError, making it less likely that third party modules get it wrong. And probably also something for the rare cases that the 'abort' event is used. This can be done without exporting the AbortError class.

    Btw, this issue is really about standardizing the rejected error, be it through always using DOMException or some other means. I'm naturally inclined towards DOMException since it better aligns with browsers, but if it is a clear no-go, then there are still other ways to improve the situation.

  8. benjamingr commented on Nov 2, 2021

    @benjamingr
    Member

    I think we are all very much in favor of standardizing third-party thrown exceptions and providing guidance to people authoring async APIs with cancellation. I think that's a good idea and I don't think that was contested.

    I think that is mostly blocked on someone doing the work. If exposing errors is a problem (I think it can be done and think the block is just technical) I am fine with exposing it elsewhere.

  9. added
    errorsIssues and PRs related to JavaScript errors originating in Node.js core.
    on Nov 6, 2021
  10. domenic commented on Nov 11, 2021

    @domenic
    Contributor

    I saw claims like this a few times:

    If you look at the spec example it encourages checking .name rather than instanceof (for the aforementioned reasons) that is quite intentional.

    I can state that is not intentional, and using .name is not the recommended way. That one example in the DOM spec is just an example. All coding styles are valid and OK on the web; instanceof, for example, is fine (if you're working within a single realm). As is checking err.code === DOMException.ABORT_ERR.

    I do think it would be beneficial to the ecosystem if Node didn't create its own separate AbortError type, and instead used and encouraged the standard DOMException. I think as @kanongil points out the fact that there are two is going to cause a lot of ecosystem confusion, as e.g. code that wants to be web/Deno compatible uses DOMException, and code which doesn't care about such compatibility uses Node's one-off version, and so on.

  11. ljharb commented on Nov 11, 2021

    @ljharb
    SponsorMember

    Why would it be a good thing for environments without a DOM to have or use a DOM exception? If it was meant to be a universal exception type then it seems it was unwisely named.

  12. domenic commented on Nov 11, 2021

    @domenic
    Contributor

    Yes, it's pretty easy to complain about names of things in the JavaScript ecosystem; as I'm sure you're aware we haven't named everything optimally in its 20+ year history.

  13. benjamingr commented on Nov 12, 2021

    @benjamingr
    Member

    @domenic to be clear the thing blocking DOMExceptions for AbortErrors in Node.js isn't the fact people don't like DOMExceptions (we already ship DOMExceptions anyway).

    It's the fact DOMExceptions become a dependency of libraries (like readable-stream) that are explicitly designed to run in non-modern Node.js environments and porting DOMException there would be hard.

    The current solution is a compromise that enabled us to adopt AbortSignals in all APIs while staying spec-compliant. Obviously we would be happier shipping less kinds of errors for the same thing :)

    @ljharb

    Why would it be a good thing for environments without a DOM to have or use a DOM exception?

    It's a communications thing but IMO the DOMness of the exception is "this exception comes from a DOM API" rather than requiring a DOM tree.

  14. benjamingr commented on Nov 12, 2021

    @benjamingr
    Member

    Oh, and regarding:

    I saw claims like this a few times: I can state that is not intentional, and using .name is not the recommended way. That one example in the DOM spec is just an example.

    @domenic I think we got that feedback/best-practice from @jakearchibald both in the AbortError meeting regarding it and back when cancellable fetch was discussed since it inter-ops between realms and is guaranteed.

    If there is other preferred guidance we are happy to look at alternatives (though it would have been great a year ago).

    (Also note I am happy to ping you more on these issues but I know you're busy and remember you don't like direct pings so I've been limiting the amount of pings to cases spec-compliance was on the line)

    (And of course the thing that blocked using DOMExceptions wasn't their name anyway it was the difficulty vendoring)

  15. ljharb commented on Nov 12, 2021

    @ljharb
    SponsorMember

    @benjamingr "came from a DOM API" means we'd be expecting users to know the difference between specifications, something web browser representatives have repeatedly insisted they do not and should not understand.

    I'm not saying the name of DOMException is the issue; as much as it is that AbortError inherits from it. Is it really too late for the web to fix that?

  16. 43 remaining items

  17. Jamesernator commented on Jun 18, 2023

    @Jamesernator

    It's unfortunate however Node.js can't take all its APIs and change their exception surface from "a standard node error with a .code property" to "whatever the user can throw to you"

    Why not? The only way to observe this is if you pass a signal that throws a different error, in which case you are presumably expecting that signal's error to propogate. Again this is a major part of the the reason that .abort accepts a reason at all.

    and not to the better.

    I disagree, being able to signal different kinds of exceptions through is useful. Like AbortSignal itself already offers AbortSignal.timeout which throws a TimeoutError instead so that one can discrimate such cases.

    It basically means every error handling code that may work with signals must expect anything to be thrown from whoever owns the controller.

    Code that sees exceptions it doesn't recognize should just rethrow them and stop continuing work, this is basic code design that has been understood for decades at this point.

  18. benjamingr commented on Jun 19, 2023

    @benjamingr
    Member

    Why not? The only way to observe this is if you pass a signal that throws a different error, in which case you are presumably expecting that signal's error to propogate.

    Yes but you do not own the controller just the signal. So someone from the outside can change your API surface.

    If you have error handling code that does something like:

    function myCode({ signal }) {
      try {
        await someFunction({ signal });
      } catch (e) {
         const aborted = someCheck(e); // e.g. e.name === 'AbortError' || e.code === 'ECONNABORTED";
         if (!aborted) throw e;
         else handleCancellation(e);
         const retryable = someOtherCheck(e); // e.g. e.code === 'EAGAIN'
         await retryLogic(e);
         throw e;
      }
    }

    In this case - every error may become unrecoverable and unexpected from the signal. The controller isn't aware of all its consumers. Let's say my code does something like:

    const ac = new AbortController();
    startServer(someData, ac); // gets AC to abort on request
    await myCode({ signal: ac.signal });

    If startServer (potentially one library) changes the signal to throw a different error the completely unaware myCode (potentially another library) has to change its logic to deal with it. It basically increases the expected error surface of the method to anything. A lot of people writing Node.js rely on being able to know what errors are thrown from APIs so I am very unsure we can say "our APIs may reject/error with anything now if you use our cancellation primitive" and get away with it.

    and not to the better.
    I disagree, being able to signal different kinds of exceptions through is useful.

    No argument regarding it being useful the disagreement is only about the core APIs rejection/error API surface. Something being useful doesn't mean it doesn't come with painful tradeoffs and I believe Node.js can't make this one for the reason above.

    Code that sees exceptions it doesn't recognize should just rethrow them and stop continuing work, this is basic code design that has been understood for decades at this point.

    You are telling me that people don't write great code - as the platform I'm not sure what to say other than "Hyrum's law". There are many things I wish we could discourage more in terms of the code people write (rejecting/throwing with strings anyone?).

    Node servers crash when an exception happens, crashes can cause a DoS, cascading failures, financial damage etc. If some library I don't control using core APIs decides to abort a controller with a new type of cancellation/error - I get woken up at night (bad) or go down (even worse). I got woken up for forgetting to filter cancellation errors correctly from instrumentation back in 2014 times ^^

    (Ironically enough (all parties were for it iirc), if we had third party cancellation this wouldn't be an issue ^^)

  19. benjamingr commented on Jun 19, 2023

    @benjamingr
    Member

    (Also @domenic as you 🚀 ed @Jamesernator comment, I want to say that while I disagree with James's point I do see it. I'm happy to change my mind, debate this and hear more arguments. Your inputs here are always valuable and respected. I hope the ±10 years we've interacted about these sort of issues are enough to convince you I am sincere in that and am always happy to hear you out)

  20. benjamingr commented on Jun 19, 2023

    @benjamingr
    Member

    And let me ask for some data:

    @mcollina @ronag from the readable-stream point of view is DOMException in Node.js old enough that in terms of support we can ship readable-stream relying on it being available?

    From the API side, I'm wondering if there is some way to find out how common "I expect this list of errors" code is as well as find out how many people treat cancellation differently than exceptions in their error handling code. I'm happy to hear ideas on how to measure this and change my mind if I'm assuming Hyrum's law and being pessimistic. One thing we may be able to try is a flag to change the behavior and then to ask people to report issues with it. I'll also see if I can ask the Node.js using teams/groups in Microsoft to check.

  21. Jamesernator commented on Jun 19, 2023

    @Jamesernator

    So someone from the outside can change your API surface.

    Yes, but again again this is the whole point of being able to signal any error.

    If you have error handling code that does something like:

    function myCode({ signal }) {
      try {
        await someFunction({ signal });
      } catch (e) {
         const aborted = someCheck(e); // e.g. e.name === 'AbortError' || e.code === 'ECONNABORTED";
         if (!aborted) throw e;
         else handleCancellation(e);
         const retryable = someOtherCheck(e); // e.g. e.code === 'EAGAIN'
         await retryLogic(e);
         throw e;
      }
    }

    This is exactly the sort've fragile pattern I was talking about, you shouldn't be handling cancellation specifically, EVERY exception that can't be handled explicitly should result in the work being cancelled not simply those with name AbortError.

    A better pattern is to precisely reverse this sort've pattern:

    function myCode({ signal }) {
      try {
        await someFunction({ signal });
      // Note we don't handle cancellation specifically at all
      } catch (e) {
        // If it's not a retry, then we don't know to handle it, so we just cleanup and stop work
        if (e.code !== "EAGAIN") {
          await cancelWorkAndCleanup();
          throw e;
        }
        // Whatever to retry
        await retry(...);
      }
    }

    If startServer (potentially one library) changes the signal to throw a different error the completely unaware myCode (potentially another library) has to change its logic to deal with it.

    If you're giving startServer full control of the AbortController then yes it can throw anything, this is just the nature of allowing .abort to take anything.

    If you don't want this, pass a different AbortController and follow it instead:

    // Yes, I don't like how verbose this either, the AbortController/AbortSignal API does leave
    // a lot to be desired in the sugar department
    
    const ac = new AbortController();
    const serverController = new AbortController();
    serverController.addEventListener("abort", () => ac.abort(new WhateverIWantToAbortWith()));
    startServer(someData, serverController); // gets AC to abort on request
    await myCode({ signal: ac.signal });

    If some library I don't control using core APIs decides to abort a controller with a new type of cancellation/error - I get woken up at night (bad) or go down (even worse). I got woken up for forgetting to filter cancellation errors correctly from instrumentation back in 2014 times ^^

    There are general problems with JS async error handling that leaves a lot to be desired, but generally speaking cancellation errors shouldn't be propagating to a point where instrumentation could even observe them.

    I don't know what cancellation primitives were around in 2014, but for AbortController if you create an abort controller and pass the signal around, it should be expected that whatever you pass it to might raise an exception.

    Like in the given example from before, given the code seems to be top level one should be prepared to handle the exception:

    const ac = new AbortController();
    startServer(someData, ac); // gets AC to abort on request
    
    // We handle cancellation at this level because we are the ones who are prepared to handle cancellation
    try {
        await myCode({ signal: ac.signal });
    } catch (e) {
        if (e === ac.signal.reason) {
            // The code has been cancelled, so we don't need to throw an error
        }
    }
  22. Jamesernator commented on Jun 19, 2023

    @Jamesernator

    Something else I do think is worth pointing out in all of this, is that for the most part users shouldn't be needing to handle cancellation at all in intermediate functions because generally speaking user code will be delegating to Node code that actually acts as sinks of AbortSignals.

    Like to demonstrate what I mean, if we write something that can be cancelled:

    async function doSomething({ signal }) {
       // ...
    }

    then internally in most cases doSomething will be passing the signal to functions created by Node which act as sinks of AbortSignal, e.g. things like fetch or fs, etc, etc

    async function doSomething({ signal }) {
       // ...
       await fetch(..., { signal });
       
       // ...
       await fsp.readFile(..., { signal });
       
       // ...
       await timerPromises.setTimeout(..., { signal });
    }

    here users don't need to handle cancellation at all, they just let the various Node APIs throw the error and it'll propagate up naturally.

    Also, usually speaking when users are implementing abortable APIs directly, the web-like behaviour is basically encouraged thanks to the exposure of signal.reason and .throwIfAborted() (also mdn docs recommend it, so most users will fall into this path as well):

    // Supposing userland implemented timerPromises.setTimeout themselves 
    function setTimeout(delay, { signal }={}) {
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => resolve(), delay);
            signal?.addEventListener("abort", () => {
                clearTimeout(timeout);
                // Note that the easiest available option here is to use signal.reason
                reject(signal.reason);
            });
        });
    }
  23. benjamingr commented on Jun 20, 2023

    @benjamingr
    Member

    @Jamesernator you are giving me examples of how one can author code to avoid pitfalls this is potentially creating, my argument hasn't been that it isn't possible to write code that works after this change it's that a bunch of people won't and likely already don't. That was the whole bit about Hyrum's law and errors being part of the API surface (enough that error code changes in Node are semver major and we "broke the ecosystem" a few times in the past before (and after) that).

    (Also it's depressing to me that we've given people an API where even experienced developers debating the semantics of these very APIs forget to remove the event listener on the signal, myself included sometimes)

  24. Jamesernator commented on Jun 20, 2023

    @Jamesernator

    after this change it's that a bunch of people won't and likely already don't.

    Well they certainly won't if Node decides to actively support behaviour that is divergent from what web browsers implement and the spec recommends.

    Also .reason wasn't initially part of web implementations either, yet they managed to ship this change perfectly fine. I don't see why Node should be in anyway special here.

    That was the whole bit about Hyrum's law and errors being part of the API surface (enough that error code changes in Node are semver major and we "broke the ecosystem" a few times in the past before (and after) that).

    I'm not suggesting this shouldn't be a semver major change, though I am wary of the fact that the longer this behaviour is around the more entrenched it becomes.

    And it's not like people in Node can even handle such errors in a consistent way anyway, as I pointed out with my earlier example. Ultimately if people rely on Node's error behaviour, and a library changes to use fetch or some other web API, then they will be hit with the web behaviour anyway.

    forget to remove the event listener on the signal, myself included sometimes)

    Yes, I usually use a helper that cleans up the listener. There are suggestions for this problem in the DOM spec, though they all have the dreaded needs implemeter interest tag.

    (Also it's depressing to me that we've given people an API where even experienced developers debating the semantics of these very APIs

    I don't want this to come across as an attack on anyone, but I do really think whatwg dropped the ball when designing this API. It's beyond me why the fairly obvious problems were not considered at the time, and still fail to have implementer interest to fix.

    It also has always strongly bothered me that from the get go of AbortSignal, the spec had a special hook to specifically avoid the event behaviour which doesn't suffer any of the same problems.

  25. benjamingr commented on Jun 21, 2023

    @benjamingr
    Member

    @Jamesernator

    Also .reason wasn't initially part of web implementations either, yet they managed to ship this change perfectly fine. I don't see why Node should be in anyway special here.

    Well, the web has a lot fewer APIs than Node that work with AbortSignal and (arguably) a lot more use cases where you'd need to abort ongoing actions.

    As I mentioned I am happy to change my mind if my intuition of "this breaks things" is wrong and I'm not sure how to get the data

    It also has always strongly bothered me that from the get go of AbortSignal, the spec had a special hook to specifically avoid the event behaviour which doesn't suffer any of the same problems.

    There are actually two now whatwg/dom#1195 the recommendation is (?) to do AbortSignal.any([signal]).addEventListener. Honestly this sucks and I can complain but I don't have the time to fix this and the reason it hasn't been fixed/addressed isn't bad faith but funding - I'm confident that if someone spent a bunch of time improving this it will be improved.

    As Symbol.dispose/Symbol.asyncDispose are stage 3 we may move to those APIs for resource management (streams, file handles, servers etc) and deprecate/remove AbortSignal support in those APIs (but not ones that signal the cancellation of an action, we'll keep using AbortSignal for those). For AbortSignal we may want to provide a helper that uses our internal (unfortunate) machinery we have for "non stoppable and weakly held event listeners" that user-land can't make atm spec wise otherwise (both weak and "unstoppable" events not being part of the spec, again somewhat for funding).

  26. mcollina commented on Jun 29, 2023

    @mcollina
    SponsorMember

    Currently readable-stream supports down to v12. However, the next release would likely cut down to v18, so this can land in a semver-major change.

  27. tilgovi commented on Oct 22, 2024

    @tilgovi
    Contributor

    Coming back to check in on this issue. In my own code, most of my use of Node.js APIs that take signals was using Node.js streams. I've switched these all to use the Web Streams API and now I can reliably compare exceptions to the abort reason.

    I do think it's pretty confusing that Node.js has a mix of behaviors here. If it isn't already too entrenched to consider changing this, maybe we want to try to do it for v24 now that the v23.x branch is cut.

  28. github-actions commented on Jun 26, 2026

    @github-actions
    Contributor

    This issue has been marked as stale due to 210 days of inactivity.
    It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.

  29. added
    staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.
    on Jun 26, 2026
  30. github-actions commented on Jul 27, 2026

    @github-actions
    Contributor

    This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 120 days).
    If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.

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

    errorsIssues and PRs related to JavaScript errors originating in Node.js core.staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions