-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
Make AbortError public #38361
Comments
I'm a bit torn on it. There is no |
Our AbortError is a Node.js specific API - it behaves like but isn't actually a DOMException or behave like the standard one. Like you said, AbortErrors aren't actually part of the standard. That said: I do see the value in exporting it to userland to help make it easy to construct AbortErrors. Wasn't there an effort to make the whole errors module public? What happened to that? That would implicitly make |
@benjamingr, any additional context you can provide on this would be nice to read up on. Was there a thread somewhere? |
I am looking into integrating aborts in the hapijs ecosystem, and find that a standardised In browsers this is standardised through As it is, I guess we will end up creating our own |
That is totally fine as long as you have the right Node.js has two AbortErrors mainly so that it can vendor parts (like streams or events) without having to ship DOMException as a dependency. APIs will throw the easy-to-vendor one when they are allowed and the DOMException one when they are required by a web specification Node.js is implementing. I think the next step if you're up for it for exportable AbortError is to work on #14554 |
@benjamingr #14554 already seems to have a failed implementation in #38575 - why would a second go at it work? |
@kanongil that PR was blocked on "Things require further consideration" (like in #38575 (review) and #38575 (review) ) . It wasn't blocked on "this is a bad idea" and I suspect one can pick up and finish the work (most easily by exposing a smaller subset). |
Also please keep in mind that when writing code for a platform like Node.js used by millions of developers that is run on billions of devices - adding APIs (especially ones that are prone to these discussions) is challenging. There is a lot of hesitation and considerations so that things don't end up causing harm. This isn't a bad thing - it is quite common for changes (e.g. promise APIs or workers) to succeed on the third or fifth attempt. That's because the problem is hard and we're all (mostly) volunteers. |
I think this is generally okay if people are careful, but I am wary of the fact this ad-hoc approach does make it rather easy for people to footgun, especially if many libraries need to implement their own
|
This is done to prepare user code for signal.reason, which will allow custom errors to be thrown on aborts. Custom errors means that it will not be possible to conclusively determine if an error is from an `ac.abort()`, just by looking at it. By not declaring what error is thrown, node is also free to change it to `new DOMException(message, 'AbortError')` in a future release. This also avoids the possible addition of a public `AbortError` to node. The thrown errors will remain instances of the internal `AbortError` for now, to avoid effecting existing user code. While signal.aborted can be used to detect aborted errors in most cases, it does fully not work for the stream pipeline API, where individual streams are destroyed with `stream.destroy(new AbortError())`. Here the stream can no longer fully detect aborts. However, I don't think this is ever relevant, as streams should always perform the same cleanup logic, regardless of what error is passed. If it doesn't support a signal option, it does not make sense to include logic to handle signal aborts. Refs: nodejs#40692 Refs: nodejs#38361 Refs: whatwg/dom#1027
I'm in significant need of this and had to do the following hack: import {EventEmitter} from "events"
let AbortError
try {
const ctrl = new AbortController()
ctrl.abort()
on(new EventEmitter(), "", {signal: ctrl.signal})
} catch (e) {
AbortError = e.constructor
}
I use this broadly for signaling cancellation after operations like dynamic imports and raw syscalls that genuinely can't be cancelled once requested, so I can still honor the request in the same way Node's native APIs do. As an example: export async function *chunkIterator(file, {maxChunkSize = 65536, signal} = {}) {
const handle = await fs.open(file, "r")
const cache = new Uint8Array(maxChunkSize * 2)
let queued = 0
try {
while (true) {
if (signal?.aborted) throw new AbortError()
const {bytesRead} = await handle.read(cache, queued)
if (bytesRead === 0) break
queued += bytesRead
if (queued >= maxChunkSize) {
queued -= maxChunkSize
yield cache.subarray(0, maxChunkSize)
}
}
if (queued > 0) yield cache.subarray(queued)
} finally {
await handle.close()
}
}
The only alternative for me is to literally just reimplement the class, which would admittedly be rather brittle and potentially run into subtle incompatibilities down the road if/when those appear. And that's a risk I really don't feel like dealing with. |
Just to be clear there are at least two different classes of "AbortError" in Node, both the Currently really only |
The most safe way to test for abort errors is to check for But you still need to throw something. For a more web-safe approach |
What if the signal aborts, but in a Note that I don't really care about web safety or compatibility here - this isn't for a public library. |
Is there a reason to bother checking both? Like if the idea is that some lower down code might violate the try {
// Do some work
await someWork();
} finally {
// Doesn't matter if someWork() threw a different kind of error
// or anything, this will throw the AbortError regardless if the
// abortSignal is currently aborted
abortSignal.throwIfAborted();
}
Even without shipping to the web, const AbortError = /* Get the AbortError like you did above */;
const controller = new AbortController();
controller.abort();
controller.signal.reason instanceof DOMException; // true
controller.signal.reason instanceof AbortError; // false |
There has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be closed 6 months after the last non-automated comment. For more information on how the project manages feature requests, please consult the feature request management document. |
There has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be closed 6 months after the last non-automated comment. For more information on how the project manages feature requests, please consult the feature request management document. |
@benjamingr What's the status of this? I see you've removed the stale label twice, but the highly related #14554 went stale and was ultimately closed. Hoping for an update since it's been almost a year since the last meaningful status update. |
I came to this issue trying to detect/throw an AbortError. From my further research, it seems that there is no such thing as an AbortError class; only an “AbortError” DOMException. In 2023, in Node 18, I think it's pretty straightforward to create the so-called // Option 1 - Use an AbortController.
// This returns an Error object with the default message.
function createAbortError() {
const c = new AbortController()
c.abort()
return c.signal.reason
}
// Option 2 - Use a DOMException.
// This approach lets you specify a custom message.
function createAbortErrorWithMessage(message) {
return new DOMException(message, 'AbortError')
} function isAbortError(e) {
return e.name === "AbortError";
} |
I agree with @dtinth – there is no longer a need for this. It is entirely replaced by the inclusion of The only maintained release line that this is relevant for is v16, which is in maintenance mode and EOL in little more than 3 weeks. |
Would be useful to be able to throw
AbortError
s in userland.The text was updated successfully, but these errors were encountered: