-
Notifications
You must be signed in to change notification settings - Fork 6
Why ? #2
Comments
They can already, and very frequently, do this with One common use case is JSON.parse, where you don't care what the error is, just "if it's not json, do nothing; if it is, parse it". |
Hi @ljharb, thanks for the input. I understand your example and I am convinced that we should log the error, maybe the data, to actually know that something unexpected happened. Unhandled errors are a bad practice, pushing the possibility to hide them as a language feature is dangerous. Side note: devs can have some input about unused vars by using the rule no-unused-vars from eslint. |
This proposal means that |
Also, omitting the binding makes it more explicit that you are intending to discard the error. |
As an example of its usefulness, just today I wrote code like function hasCompatibleWebAPI() {
try {
// try to exercise the edge cases of the API
return true;
} catch(unused) {
return false;
} finally {
// clean up any effects that our usage of the API may have had
}
} which would have made use of this feature. |
Thanks @michaelficarra. I think the real problem is not «how to help developers hide unused bindings» but «how to help developers handle errors correctly». In your example, you should actually have a trace of what happened, and that is why error handling is important, and must not be hidden. If you prefer to escape from the error handling tyranny, you should make a babel or whatever plugin, but not push a language feature that will allow devs to forget the real nature of try/catch statements. (I am really sorry about being stubborn about this) |
Nothing's being hidden. Using this proposal is simply a cleaner form of including but ignoring the catch binding, which devs have always both been able to do and done. |
I can imagine it makes sense with some sort of try catch expression (anyone knows such proposal please link here): @michaelficarra @ljharb try {
// do stuff
}
catch (presumablyXError) { you have a chance to assert your assumption and may be log something which is always a good practice. It's one thing if you don't, which is normal, it's another thing to encourage such habit by language support. @bakkot What needs to be explicit is not that you omit, but why you do so. So often there should be a comment explaining anyway. IMO if you have to type a comment, why not do a better thing which is logging that message with the once we start adding stuff like this there will be all sorts of proposal whose sole purpose is to save keystrokes. Conciseness is good, but Occam's razor can be applied here. |
An interesting thread happened in the es-discuss mailing list: https://esdiscuss.org/topic/an-operator-for-ignoring-any-exceptions I join Kai Zhu and Frederick Stark here, and in my opinion, their comments also applies to the optional catch binding proposal. |
Wrote this very late last night. Sorry for the sarcasm. It's uncalled for. |
@danny-andrews This proposal makes JS more safe, because it allows the developer's intentions to be more explicitly communicated. |
I just found this article about some use cases for this proposal from Dr. Axel Rauschmayer (this issue is actually linked at the bottom of the article). I am with him in this part:
|
the thing that many people seem to oppose is swallowing exceptions. So I think @ljharb you should elaborate that, specifically response to the practices in @SiegfriedEhret's comment, before going further so say how this PR helps swallow exceptions.
This is where any planned idea for multiple catches/ pattern matching or similar should be considered while assessing this, and I'd like to see anyone who's involved in such proposal to share their view here. My other point in addition to those is that people/ourselves are just gonna abuse it, we simply can't deny nor prevent that. The cost this can potentially impose on code quality across the ecosystem far outweigh a few cases when it is useful. |
@sonhanguyen You can already intentionally swallow exceptions, so it's simply not necessary to justify that use case - it exists, it's used, it's valuable to some. Anyone who uses this proposed feature will be using it instead of |
@ljharb you said that already but imho "it already happens" is not the same as "it's justifiable". |
The cost of this proposal on the language itself is miniscule. It's a tiny syntax change with no new semantics. So the root question seems to be whether this proposal will encourage developers to handle fewer errors when it makes sense to do so. This seems like a hard question to answer definitively without data. My gut feeling is that the sum of bad Meanwhile, developers gain expressiveness and a segment of developers who like this pattern for certain uses get a nice syntax sugar. On the whole, seems like a positive to me. |
occasionally I need to write callbacks like Imagine what a javascript book will be 10 years from now when lots edge cases like this have been added to the language. Every page will have a footnote "by the way you can omit this when such and such". I don't think that makes a good learning experience. |
Another thought along the lines of what @sonhanguyen is saying above: allow for |
The similarity would make it harder to know if the omission of a catch binding was intentional or a mistake. |
@ljharb Just because you can already do something unadvisable, doesn't mean we should make it less painful to do so by adding a language feature to support it. It's a good thing that linters complain about these unused variables. It forces us to give a reason for not dealing with them via a comment or something else. This spec puts convenience over best practices which never turns out well. The core issue here and the reason programmers find themselves ignoring errors in Javascript so often (as you've already alluded to) is because core language features (e.g. const result = JSON.parse(fileContents);
if (result instanceof SyntaxError) {
console.log('hey bro, your JSON file is not JSON');
} else {
doTheThing(result);
} There's an easy fix for this, however. Wrap const parseJSON = (...args) => {
try {
return fn(...args);
} catch(e) {
return e;
}
}; |
@bterlson I have to disagree, especially for junior programmers. If I write: let result;
try {
result = JSON.parse(fileContents);
} catch(e) {} and get a linter error, I will be forced to think twice about what I'm doing. Sure, I might just add a comment 90% of the time (which is still better than doing nothing). But the other 10% I might be prodded to log the error or wrap the error in my own. |
Since engineering is really about evaluating PROs and CONs, I say we lay them out here and see which wins out. Here they are, as I see it: PROs:
CONs:
I really can't comprehend how the PROs outweigh the CONs here. It really seems like we're just adding a feature to save character strokes, which is almost never the right approach since we spend ~10x more time reading code than writing it. p.s. I'm not trying to create a straw-man here, so please let me know if there are any PRO's I'm missing, or if I have mischaracterized the one I listed. |
Errors that should be ignored are fine to ignore, and errors that are already ignored will continue to be ignored with or without this proposal; considering this a "con" doesn't make any sense. As for "adds more complexity", every proposal does that, and that's not a sufficient reason on its face to combat any proposal - and the weight of that is already thus considered with every proposal. |
Your "PRO" isn't really a strawman, though I'd put emphasis on the "explicitly" more than "tersely". But your later summary is: you summarize as "we're just adding a feature to save character strokes", but that has little to do with it. It's about expressing intent clearly. And making it easier to clearly express intent, at a language level, is indeed mostly for the benefit of readers of the code, not writers. |
You missed my point. This spec adds syntax to Javascript specifically for the purpose of ignoring errors. The "CON" is that it removes the friction involved in taking this action.
Actually, that is a sufficient reason on its face to combat a proposal. The burden is on the proposal author to demonstrate that the improvements contained therein legitimize adding said complexity. My argument is that they don't. |
You're correct in saying that this spec makes it easier to clearly express your intent to ignore an error, but it absolves you of giving a reason why, which is what is really important when taking an inadvisable action. |
The spec has never required you to give a reason why you are ignoring an error. |
@ljharb
you'd be confident whoever wrote that explicitly ignored the exception, right? Does that mean you could factor out 100% the chance of this having something to do with the bizarre issue you are trying to debug? If not then what's the benefit of being "explicit" here? |
about And I wonder how is |
@ljharb huh, I also think |
That won't be true for new JS devs tho, which will outnumber existing ones in the long run :-) |
You know what will be true for new JS devs? Having an extra special case to learn when trying to master what should be a straight-forward construct! |
@michaelficarra We have a pretty standard cross-language convention for marking an argument as unused/unimportant. It's function hasCompatibleWebAPI() {
try {
// try to exercise the edge cases of the API
return true;
} catch(_) {
return false;
} finally {
// clean up any effects that our usage of the API may have had
}
} I've made it explicit that I don't care about the error object. No language change necessary! SHUT IT DOWN BOYS! 🔥 🔫 💥 (Seriously, though, let's not add this superfluous feature to an already complex language. Please.) p.s. You should have the following rule in your .eslintrc.yaml: no-unused-vars:
- error
- argsIgnorePattern: ^_
caughtErrors: all |
@danny-andrews this makes the construct more straightforward; it's nonsensical to be forced to create a binding when you don't intend to use it. |
@ljharb thanks for fielding the comments, really appreciate your calm tone and kind feedback @danny-andrews I suppose that if we really want to make our concerns heard, it might be best to direct them to folks on the TC39 - especially community-oriented reps - such as the JS Foundation rep @maggiepint At the same time, not sure how they might be expected to weight the opinion of a few passers by. The PHP world has an interesting approach in that their new features are actually voted upon thru democratic RFCs. Not saying that's necessarily a better approach, but it would be interesting to get a statistical sample to sense of what devs outside of the elite world of the TC39 generally think about new developments. On the bright side, this is consistent with Python's except: which allows for omitting a param. On the other hand, Java and PHP don't seem to have the same feature. I also don't quite agree with http://2ality.com/2017/08/optional-catch-binding.html about always at least logging an error - no need to spew spurious data to the console. With that said, while I've ran into plenty of harmless errors, every time I've entirely silenced an error I've regarded it as a temporary hack to be ultimately prevented (sometimes by fixing upstream code) but maybe I haven't coded enough. Anyhow, worried about the complexity in JavaScript's future, but on the bright side, hey, job security... |
@jcrben, all of michealficarra, bterlson, ljharb, and myself are on TC39. |
Ah, good to hear. Off-topic, but wish I could find a maintained list along with affiliations... http://tc39wiki.calculist.org/about/people/ is clearly ancient By the way, this really does open the question from @sonhanguyen about |
|
@sonhanguyen a mix of the two. it's not TC39's place to tell people how to program; similarly, it's part of our job to ensure that the language encourages good practices. Using an unused variable to swallow errors is not a good practice; but it will be fine to use |
I looked into how other functional languages handle this case where you don't want to use the exception: try
failwith "fail"
with
| Failure msg -> "caught: " + msg
| MyFSharpError1 msg -> " MyFSharpError1: " + msg
| :? System.InvalidOperationException as ex -> "unexpected" // ex is unused here. nbd. OCaml: exception NotFound of string
let getItem theList = raise (NotFound "oh")
let result =
try getItem [1; 2; 3]
with
| NotFound e -> print_endline "Item not found!" (* e is unused. Whatever, yo. *) Elm: import Html exposing (text)
view userInputAge =
case String.toInt userInputAge of
Err msg -> text "Not OK!" -- msg is unused
Ok age ->
text "OK!"
main = view "34" I realize these languages are statically typed, so listing the exception variable explicitly comes with the territory, but the fact that you are ignoring it is actually pretty apparent. |
Ruby: def raise
raise Error.new
end
foo = raise rescue nil Which is dynamically typed, and thus perhaps a better exemplar for JS. |
Yep, and that indiscriminately swallows errors up, regardless of their type or origin. Wouldn't you agree that this: let jsonData;
try {
jsonData = JSON.prase(str);
} catch (err) {
if (!(err instanceof SyntaxError)) {
throw err;
}
} is better than this: let jsonData;
try {
jsonData = JSON.prase(str);
} catch(e) {} Note: I left a little surprise in my examples. 😄 In the first example, the I realize that requiring a variable binding doesn't force you to actually use it, but it should at least raise your eyebrows when you see a linter error about it, and that counts for something I think. I also realize it's verbose, but I'd take verbosity over swallowing errors any day! Maybe pattern matching or conditional catch clauses can clean this up in the future, but until then, I think it's better to not add a construct which gives you an excuse to be lazy. p.s. I think we should be aspiring for higher standard than Ruby (I realize we're talking about JavaScript, but hey let's not make it any worse, right?). It's a fine scripting languages, but doesn't scale worth shit because of it's numerous convenient, seductive escape hatches. |
That's a choice you're welcome to make, but the comparison is actually to: let jsonData;
try {
jsonData = JSON.prase(str);
} catch (e) {} In which case, I think the one that omits the binding is objectively better. |
@danny-andrews I promise there will be a lint rule to flag identifier-less catch, fwiw. |
No. If I wouldn't put (The thing with the typo really feels like a red herring to me. Plenty of other tools and the most trivial of tests will catch that. I'm not going to ship code whose only purpose is to check that I don't have a typo in a method name; I'm just going to confirm that I don't have a typo.) |
Better than the one which checks the error type? |
@danny-andrews personally i'd prefer the one that checks the type - but that's not the alternative here. Checking the type is a personal subjective choice, and the language shouldn't (and doesn't currently) force you to do that. This is a comparison strictly between |
How can a test catch an error being swallowed? You wouldn't assert that a syntax error is thrown when you make a typo. That's silly. I also don't know what "other tools" you are referring to except for maybe Flow, but even then, if you calling a library function which doesn't have Flow definitions, you would have no way to catch the typo at compile-time. But I'll give you that it was a bad example. But what if, for instance, you did some other possibly-throwing operation in the same |
I can test that if I pass valid JSON I get some expected result instead of
Flow and TypeScript would both suffice, yes.
But I am not doing some other possibly-throwing operation in the same I'm not trying to claim we should always discard the exception. Heaven forfend! I'm just claiming that there are some cases when a catch block might reasonably not use the error object, and that in those cases the language ought not force you to create a binding for it anyway. |
Okay, I think we're arguing past each other. I think we're talking about two different 'better's:
It's simply a matter of determining which "better" is more important. I'm arguing the latter, you, the former. |
Lint errors can apply equally well to either form; as for "looking weird", that's pretty subjective. |
What if I used the word "unclear." You said before that |
Sure, I think that "with the binding" is less clear as to the intention of the dev than "without the binding". |
I think I'm not as opposed to this proposal as I initially thought. I think the empty-block examples threw me off. Like, if we were comparing: catch {
console.log('Something bad happened'); // I'm not using the error object, but at least I'm not ignoring the error entirely
} to: catch (e) {
console.log('Something bad happened');
} I'd be more likely to accept it. I still like to see that |
In closing: I don't like this proposal because it makes an anti-pattern feel more natural. But it is a pretty small change, so w/e. 🤷♂️ |
Also, sorry for all the noise, and thanks for the discussion. I hope I haven't been annoying. |
Hello @michaelficarra !
I saw earlier today that this spec is now at stage 3.
I don't understand the motivation and use-cases for it.
Actually I am pretty scared that allowing this may make devs forget about error handling and making production code untraceable etc. I think errors must be logged, traced and handled instead of being silent.
Would you mind detailing the
why
?Thanks !
The text was updated successfully, but these errors were encountered: