-
Notifications
You must be signed in to change notification settings - Fork 49
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
Comments
... 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 |
At London f2f: e.g. "always throw exceptions for these conditions..." |
@dbaron : it should say what kinds of things you should consider when deciding [which approach to take] |
e.g., XHR. throws exceptions in some cases, but handles "errors" like non-200 status codes, separately. |
@slightlyoff suggests that use of exceptions is generally a bad idea. |
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. |
FWIW, I think you generally want to consider these things:
|
And you probably don't necessarily want to study "legacy" APIs such as |
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... |
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. 👀 |
I just came across navigator.vibrate() and it reminded me of this issue.
Would it be of value to make a list of such existing (anti?)patterns? |
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 |
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. |
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.');
} |
Fetch when aborted via an AbortController (can easily be user initiated) it will not resolve with null, but instead reject with AbortError |
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. |
This is a subtle question. Given that TC39 rejected treating cancelations as something separate from errors, " @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 Whereas, if the phrasing is Where
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);
} |
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"); |
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 |
I believe it should too, mind filing an issue @plinss ? |
The platform is currently inconsistent on this, we should have principles to guide the decision...
The text was updated successfully, but these errors were encountered: