Skip to content
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

fix(fetch): use AbortError DOMException #1511

Merged
merged 2 commits into from
Jun 27, 2022

Conversation

KhafraDev
Copy link
Member

This commit uses the proper error when a request is aborted. A WPT actually checks for this behavior.

image


Honestly I didn't know if I should have put DOMException in constants or in util. I can change it if it matters or there's a preference.

@codecov-commenter
Copy link

codecov-commenter commented Jun 23, 2022

Codecov Report

Merging #1511 (edc4f54) into main (7d9fd51) will increase coverage by 0.02%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##             main    #1511      +/-   ##
==========================================
+ Coverage   94.63%   94.65%   +0.02%     
==========================================
  Files          50       50              
  Lines        4589     4588       -1     
==========================================
  Hits         4343     4343              
+ Misses        246      245       -1     
Impacted Files Coverage Δ
lib/core/errors.js 100.00% <ø> (ø)
lib/fetch/response.js 94.21% <ø> (-0.04%) ⬇️
lib/fetch/constants.js 100.00% <100.00%> (ø)
lib/fetch/index.js 81.15% <100.00%> (-0.04%) ⬇️
lib/client.js 97.76% <0.00%> (+0.12%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7d9fd51...edc4f54. Read the comment docs.

@@ -60,7 +60,26 @@ const subresource = [
''
]

/** @type {globalThis['DOMException']} */
const DOMException = globalThis.DOMException ?? (() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a DomException in Node, no need for polyfill.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow missed this notification 😕.

DOMException is available in node globally from v17 and above, but fetch supports v16.5 and above. There's a comment detailing why this is needed a few lines below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get it with:

try {
  atob('~')
} catch (err) {
  const DOMException = Object.getPrototypeOf(err).constructor

  console.log(new DOMException('aaa'))
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in edc4f54

@LiviaMedeiros
Copy link
Contributor

Should this support user-provided reason and TimeoutError?

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearly marking that changes should br made here

@KhafraDev
Copy link
Member Author

KhafraDev commented Jun 26, 2022

Should this support user-provided reason and TimeoutError?

No, the spec requires an AbortError DOMException and the error is verbatim from Firefox.

Clearly marking that changes should br made here

I'm unsure what you mean by this

@LiviaMedeiros
Copy link
Contributor

LiviaMedeiros commented Jun 26, 2022

Clearly marking that changes should br made here

If you meant my question, it's completely not blocking. :)
Even if we decide that changes must be made, I believe it can be done in a follow-up PR instead.

No, the spec requires an AbortError DOMException and the error is verbatim from Firefox.

Do we have any way to distinguish between arbitrary AbortError and TimeoutError? If not, I'm confused with how should AbortSignal.timeout() work in userland.

AFAIK there were12 common reasons in Node.js to use AbortError so if (err.name === 'AbortError') keeps being robust, but we definitely need a way to have more information. Is there a workaround?

This feature is not widely implemented yet, and timeout was added in Firefox only in v100 so its current behaviour doesn't convince me yet.

Footnotes

  1. https://github.com/nodejs/node/issues/36084

  2. https://github.com/nodejs/node/issues/40692

@KhafraDev
Copy link
Member Author

KhafraDev commented Jun 26, 2022

Do we have any way to distinguish between arbitrary AbortError and TimeoutError? If not, I'm confused with how should AbortSignal.timeout() work in userland.

Yes, you can use the .name or .code property. However, until the spec is updated, an AbortError DOMException should still be thrown.

const abortError = new DOMException('The operation was aborted.', 'AbortError')

abortError.name // 'AbortError'

abortError.code === DOMException.ABORT_ERR

There doesn't seem to be any discussion in the fetch spec repo, this should probably be followed up there. However both Chrome & Firefox throw AbortError DOMExceptions.

AFAIK there were12 common reasons in Node.js to use AbortError so if (err.name === 'AbortError') keeps being robust, but we definitely need a way to have more information. Is there a workaround?

In the issue mentioned, Node.js does throw DOMExceptions for DOM APIs.. You can still use error.name === 'AbortError'.

@LiviaMedeiros
Copy link
Contributor

  1. Providing custom reason to AbortController.abort():
const controller = new AbortController();
setTimeout(() => controller.abort('custom reason'), 10);

fetch('https://github.com/nodejs/undici/pull/1511', {
  signal: controller.signal
}).catch(err => {
  console.error(err.name, err.code, err.message, err.cause);
});
  1. Using AbortSignal.timeout():
fetch('https://github.com/nodejs/undici/pull/1511', {
  signal: AbortSignal.timeout(10)
}).catch(err => {
  console.error(err.name, err.code, err.message, err.cause);
});

In both cases I'm getting generic AbortError ABORT_ERR The operation was aborted undefined.

As an user, in (1), I need to have a way to retrieve custom reason.
In (2), I need to have a way to retrieve TimeoutError (or Node.js-specific code with same meaning).

Is it retrievable without having an explicit reference to signal.reason? Should this be addressed in undici, or Node.js, or DOM specs?

// This acknowledges TimeoutError in Node.js and Firefox >=100; Chrome <=102 doesn't support it
AbortSignal.timeout(10).addEventListener('abort', function(err) { console.info(this.reason.name) }); // TimeoutError

@KhafraDev
Copy link
Member Author

I'm getting the same results in the browser, this should be suggested at https://github.com/whatwg/fetch

Copy link
Contributor

@LiviaMedeiros LiviaMedeiros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll go ahead and fill an issue to WHATWG.

Some references from brief searching:

My current impression (and suggestion to consider for undici implementation) on this is that having error.cause == signal.reason would be the best solution. But I might be wrong. :)

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@ronag ronag merged commit c7f527b into nodejs:main Jun 27, 2022
@KhafraDev KhafraDev deleted the abort-domexception branch June 27, 2022 15:08
metcoder95 pushed a commit to metcoder95/undici that referenced this pull request Dec 26, 2022
crysmags pushed a commit to crysmags/undici that referenced this pull request Feb 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants