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

Need guidance on returning error values vs throwing exceptions #55

Open
plinss opened this issue Apr 18, 2017 · 22 comments
Open

Need guidance on returning error values vs throwing exceptions #55

plinss opened this issue Apr 18, 2017 · 22 comments
Assignees
Labels
Status: Consensus to write We have TAG consensus about the principle but someone needs to write it (see "To Write" project) tc39-tracker Issues where TC39 input would be useful Topic: JS

Comments

@plinss
Copy link
Member

plinss commented Apr 18, 2017

The platform is currently inconsistent on this, we should have principles to guide the decision...

@dbaron
Copy link
Member

dbaron commented Jul 26, 2017

... versus returning null, zero, etc., on error.

Also rejecting promises versus resolving them with some specific value.

@plinss apparently raised this after a discussion in Houdini

@torgo
Copy link
Member

torgo commented Jul 26, 2017

At London f2f: e.g. "always throw exceptions for these conditions..."

@torgo
Copy link
Member

torgo commented Jul 26, 2017

@dbaron : it should say what kinds of things you should consider when deciding [which approach to take]

@travisleithead
Copy link
Contributor

e.g., XHR. throws exceptions in some cases, but handles "errors" like non-200 status codes, separately.

@dbaron
Copy link
Member

dbaron commented Jul 26, 2017

@slightlyoff suggests that use of exceptions is generally a bad idea.

@timbl
Copy link
Member

timbl commented Jul 26, 2017

I feel you should be able to run your program in the debugger with "Pause even on caught exceptions" on. Exceptions should not be used for returning data, ending a loop etc.

@annevk
Copy link
Member

annevk commented Jul 26, 2017

FWIW, I think you generally want to consider these things:

  • How is the API likely to evolve in the future and does it help or hurt if you throw for that case today?
  • How exceptional is the case you're considering to throw for?
  • What do ECMAScript and IDL throw for and is this analogous somehow?
  • What do other platform APIs do for similar input?

@annevk
Copy link
Member

annevk commented Jul 26, 2017

And you probably don't necessarily want to study "legacy" APIs such as XMLHttpRequest (although not throwing for non-200 is still a good thing unless your API sits at an even higher abstraction level; you still got a response after all).

@dbaron
Copy link
Member

dbaron commented Apr 17, 2019

An issue where the TAG was asked for advice on a closely-related topic is w3ctag/design-reviews#348.

That we're being asked about this sort of thing perhaps increases its priority a little...

@a2sheppy
Copy link

This is an issue that confuses developers frequently, and I wouldn't be surprised if it's the cause of a substantial number of bugs out there, in which developers think they're testing for errors but are not in fact doing so because of a misunderstanding about how the errors are being reported.

There's not much the can be done for existing specs, but if we can be consistent in the future that would help enormously with the reliably of future code. And make my life easier. Not that that's a primary objective of mine or anything.  👀

@plinss plinss added this to the 2021-02-01-week milestone Feb 1, 2021
@LeaVerou
Copy link
Member

LeaVerou commented Feb 2, 2021

I just came across navigator.vibrate() and it reminded me of this issue.

If the method was unable to vibrate because of invalid parameters, it will return false, else it returns true.

Would it be of value to make a list of such existing (anti?)patterns?

@plinss plinss assigned atanassov and unassigned dbaron Mar 2, 2021
@kenchris
Copy link
Contributor

kenchris commented Sep 1, 2021

File System Access https://wicg.github.io/file-system-access/#api-showopenfilepicker 7.4 will reject with AbortError when the user decides to close the picker without choosing a file. Some on the TAG believe this should resolve the promise instead with a null value @plinss @domenic

A similar approach is used in EyeDropper

@plinss plinss closed this as completed Sep 1, 2021
@plinss plinss reopened this Sep 1, 2021
@LeaVerou
Copy link
Member

LeaVerou commented Sep 1, 2021

@annevk
Copy link
Member

annevk commented Sep 2, 2021

I tend to agree that APIs that ask the user something shouldn't really result in an exception. It's a common enough occurrence that every caller needs to handle it and exceptions are not a great fit for that.

@tomayac
Copy link
Contributor

tomayac commented Sep 2, 2021

I dislike having to code like this:

try {
  /* Something */
} catch (err) {
  if (err.name !== 'AbortError') {
    // Actual exception handling.
    return console.error(err);
  }
  // The non-exceptional case…
  console.log('The user aborted a request.');
}

@kenchris
Copy link
Contributor

kenchris commented Sep 2, 2021

Fetch when aborted via an AbortController (can easily be user initiated) it will not resolve with null, but instead reject with AbortError

@annevk
Copy link
Member

annevk commented Sep 2, 2021

That's not an API call where you ask the user to make a decision. That's an API call to get something from the network. Not being able to reach the network is arguably exceptional, but I agree there's nuance there.

@domenic
Copy link
Member

domenic commented Sep 2, 2021

This is a subtle question. Given that TC39 rejected treating cancelations as something separate from errors, "AbortError" DOMExceptions have a special place in the ecosystem. And it might be worth relaxing the general guidance around exceptions-are-for-exceptional-situations to encompass that special place.

@annevk's idea of drawing a line between user-initiated cancelations and developer-initiated cancelations is a novel one, but I'm not sure it fully works. It feels like the categories often are intertwined, with user-initiated cancelations often causing developers to cancel something. But it might be possible to make this concrete, if we have more examples to tease out the relevant differences.

Finally, I'll mention that (as the design principles I originally wrote point out) a lot of this depends on the phrasing of the method. If it's const file = pickFile() or const color = pickColor(), then arguably, by writing that code, the developer is expecting the result to be a file/color, and the user refusing to choose one is "exceptional". Not "exceptional" as in "unlikely", but "exceptional" as in "not on the happy path".

Whereas, if the phrasing is const maybeFile = tryPickFile() or const color = pickColorOrNull(), then it's more obvious that the user not choosing a file/color is expected.

Where showFilePicker() and eyeDropper.open() fall on that spectrum... not clear.

I dislike having to code like this:

Note that ultimately your code is not that different from the alternative, i.e.

let result;
try {
  result = /* Something */
} catch (e) {
  // Actual exception handling.
  return console.error(err);
}

if (!result) {
  // The non-exceptional case…
  console.log('The user aborted a request.');
}

The alternative is a bit flatter, but I don't think the difference is fundamental. Ultimately you have three paths for your logic, and that means you're going to need three separate blocks.

@LeaVerou
Copy link
Member

LeaVerou commented Sep 2, 2021

I dislike having to code like this:

Note that ultimately your code is not that different from the alternative, i.e.

let result;
try {
  result = /* Something */
} catch (e) {
  // Actual exception handling.
  return console.error(err);
}

if (!result) {
  // The non-exceptional case…
  console.log('The user aborted a request.');
}

The alternative is a bit flatter, but I don't think the difference is fundamental. Ultimately you have three paths for your logic, and that means you're going to need three separate blocks.

Do note that it could be a lot simpler though in the common case where if the user doesn't pick something, the developer will just select a default value instead of choosing an entirely different code path:

let result;

try {
	result = await eyeDropper.open() ?? "#808080";
} catch (e) {
	// Actual exception handling.
	return console.error(err);
}

@kenchris
Copy link
Contributor

kenchris commented Sep 2, 2021

So how do you handle errors in that case? You also want a fallback value in case of errors, or if the user agent cancelled the operation (like it can with wake lock for instance - not that I need a value in that case, but it was just the example that came to my mind)

Though a bit ugly, you could do:

let result = await eyeDropper.open().catch(_ => "#808080");

@plinss
Copy link
Member Author

plinss commented Sep 2, 2021

IMO, in the case of the color picker, the open call is requesting the user to make a choice, not selecting a color is still a choice the user made, and therefore not an exceptional situation. In other languages/platforms, when a call must return a usable value, it's often named 'must', 'force', or the like, or wrapped in a 'must()' function which itself throws when a null (or other error) result is returned.

Another point, take the case where open() accepts an AbortSignal (which is something I believe it should do), how can one tell the difference between a call that was aborted via the AbortController (which should definitely throw an AbortError) vs canceled by the user?

@kenchris
Copy link
Contributor

Another point, take the case where open() accepts an AbortSignal (which is something I believe it should do)

I believe it should too, mind filing an issue @plinss ?

@LeaVerou LeaVerou added the Status: Consensus to write We have TAG consensus about the principle but someone needs to write it (see "To Write" project) label Sep 16, 2021
@LeaVerou LeaVerou added the tc39-tracker Issues where TC39 input would be useful label Apr 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Consensus to write We have TAG consensus about the principle but someone needs to write it (see "To Write" project) tc39-tracker Issues where TC39 input would be useful Topic: JS
Projects
None yet
Development

No branches or pull requests