Repository navigation
Suggestion: throws clause and typed catch clause #13219
Description
The typescript type system is helpful in most cases, but it can’t be utilized when handling exceptions.
For example:
function fn(num: number): void {
if (num === 0) {
throw "error: can't deal with 0";
}
}
The problem here is two fold (without looking through the code):
- When using this function there’s no way to know that it might throw an error
- It’s not clear what the type(s) of the error is going to be
In many scenarios these aren't really a problem, but knowing whether a function/method might throw an exception can be very useful in different scenarios, especially when using different libraries.
By introducing (optional) checked exception the type system can be utilized for exception handling.
I know that checked exceptions isn't agreed upon (for example Anders Hejlsberg), but by making it optional (and maybe inferred? more later) then it just adds the opportunity to add more information about the code which can help developers, tools and documentation.
It will also allow a better usage of meaningful custom errors for large big projects.
As all javascript runtime errors are of type Error (or extending types such as TypeError) the actual type for a function will always be type | Error.
The grammar is straightforward, a function definition can end with a throws clause followed by a type:
function fn() throws string { ... }
function fn(...) throws string | number { ... }
class MyError extends Error { ... }
function fn(...): Promise<string> throws MyError { ... }
When catching the exceptions the syntax is the same with the ability to declare the type(s) of the error:
catch(e: string | Error) { ... }
Examples:
function fn(num: number): void throws string {
if (num === 0) {
throw "error: can't deal with 0";
}
}
Here it’s clear that the function can throw an error and that the error will be a string, and so when calling this method the developer (and the compiler/IDE) is aware of it and can handle it better.
So:
fn(0);
// or
try {
fn(0);
} catch (e: string) { ... }
Compiles with no errors, but:
try {
fn(0);
} catch (e: number) { ... }
Fails to compile because number isn't string.
Control flow and error type inference
try {
fn(0);
} catch(e) {
if (typeof e === "string") {
console.log(e.length);
} else if (e instanceof Error) {
console.log(e.message);
} else if (typeof e === "string") {
console.log(e * 3); // error: Unreachable code detected
}
console.log(e * 3); // error: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type
}
function fn(num: number): void {
if (num === 0) {
throw "error: can't deal with 0";
}
}
Throws string.
function fn2(num: number) {
if (num < 0) {
throw new MyError("can only deal with positives");
}
fn(num);
}
Throws MyError | string.
However:
function fn2(num: number) {
if (num < 0) {
throw new MyError("can only deal with positives");
}
try {
fn(num);
} catch(e) {
if (typeof e === "string") {
throw new MyError(e);
}
}
}
Throws only MyError.
Activity
[-]Suggestion: Checked exceptions and typed cache clause[/-][+]Suggestion: Checked exceptions and typed catch clause[/+]DanielRosenwasser commentedon Dec 30, 2016
Just to clarify - one the ideas here is not to force users to catch the exception, but rather, to better infer the type of a catch clause variable?
nitzantomer commentedon Dec 30, 2016
Daniel Rosenwasser (@DanielRosenwasser)
Yes, users won't be forced to catch exceptions, so this is fine with the compiler (at runtime the error is thrown of course):
But it will give developers a way to express which exceptions can be thrown (would be awesome to have that when using other libraries
.d.tsfiles) and then have the compiler type guard the exception types inside the catch clause.zpdDG4gta8XKpMCd commentedon Jan 9, 2017
how is a checked throw different from
Tried<Result, Error>?instead of
nitzantomer commentedon Jan 10, 2017
Aleksey-Bykov
You're suggesting not to use
throwat all in my code and instead wrap the results (in functions that might error).This approach has a few drawbacks:
Tried<>can not choose to ignore the error.Adding
throwswill enable developers who choose to to handle errors from their code, 3rd libraries and native js.As the suggestion also requests for error inferring, all generated definition files can include the
throwsclause.It will be very convenient to know what errors a function might throw straight from the definition file instead of the current state where you need to go to the docs, for example to know which error
JSON.parsemight throw I need to go to the MDN page and read that:And this is the good case when the error is documented.
zpdDG4gta8XKpMCd commentedon Jan 10, 2017
is there a reliable way in javascript to tell apart SyntaxError from Error?
yes, it's more code, but since a bad situation is represented in an object, it can be passed around to be processed, discarded, stored or transformed into a valid result just like any other value
you can ignore tried by returning tried too, tried can be viewed as a monad, look for monadic computations
it's standard enough for your code, when it comes to 3rd party libs throwing an exception it generally means a gameover for you, because it is close to impossible to reliably recover from an exception, reason is that it can be thrown from anywhere inside the code terminating it at an arbitrary position and leaving its internal state incomplete or corrupt
there is no support for checked exceptions from JavaScript runtime, and i am afraid it cannot be implemented in typescript alone
other than that encoding an exception as a special result case is a very common practice in FP world
whereas splitting a possible outcome into 2 parts:
looks a made up difficulty
in my opinion, throw is good for failing fast and loud when nothing you can do about it, explicitly coded results are good for anything that implies a bad yet expected situation which you can recover from
zpdDG4gta8XKpMCd commentedon Jan 10, 2017
consider:
nitzantomer commentedon Jan 10, 2017
Aleksey-Bykov
My point with
JSON.parsemight throwingSyntaxErroris that I need to look the function up in the docs just to know that it might throw, and it would be easier to see that in the.d.ts.And yes, you can know that it's
SyntaxErrorwith usinginstanceof.You can represent the same bad situation with throwing an error.
You can create your own error class which extends
Errorand put all of the relevant data that you need in it.You're getting the same with less code.
Sometimes you have a long chain of function invocations and you might want to deal with some of the errors in different levels of the chain.
It will be pretty annoying to always use wrapped results (monads).
Not to mention that again, other libraries and native errors might be thrown anyway, so you might end up using both monads and try/catch.
I disagree with you, in a lot of cases you can recover from thrown errors, and if the language lets you express it better than it will be easier to do so.
Like with a lot of things in typescript, the lack of support of the feature in javascript isn't an issue.
This:
Will work as expected in javascript, just without the type annotation.
Using
throwis enough to express what you're saying: if the operation succeeded return the value, otherwise throw an error.The user of this function will then decide if he wants to deal with the possible errors or ignore them.
You can deal with only errors you thrown yourself and ignore the ones which are 3rd party for example.
zpdDG4gta8XKpMCd commentedon Jan 10, 2017
if we talking about browsers
instanceofis only good for stuff that originates from the same window/document, try it:so when you do:
you won't catch it
another problem with exceptions that they might slip into your code from far beyond of where you expect them to happen
zpdDG4gta8XKpMCd commentedon Jan 10, 2017
besides
instanceofis vulnerable to prototype inheritance, so you need to be extra cautions to always check against the final ancestorgcnew commentedon Jan 10, 2017
Aleksey-Bykov Explicitly threading errors as you suggest in monadic structures is quite hard and daunting task. It takes a lot of effort, makes the code hard to understand and requires language support / type-driven emit to be on the edge of being bearable. This is a comment comming from somebody who puts a lot of effort into popularising Haskell and FP as a whole.
It is a working alternative, especially for enthusiasts (myself included), however I don't think it's a viable option for the larger audience.
261 remaining items
gastonmorixe commentedon Aug 25, 2023
Ryan Cavanaugh (@RyanCavanaugh) would it be possible to reopen this please? Thank you
KashubaK commentedon Aug 25, 2023
I don't think there's any actionable items in regards to this specific issue at this time. I think the next step should revolve around implementing a solution that encourages error checking in hopes to create widespread adoption of error documentation. After that point I imagine this issue could be brought back up, though I am definitely curious about their thoughts on our recent comments.
I'm going to try my hand at a linter rule over the weekend. If it goes well, I'll link the repo back here and we can move that conversation there.
gastonmorixe commentedon Aug 25, 2023
I think it’s important enough to not be closed and encourage more discussion
kvenn commentedon Aug 25, 2023
Agreed Kyle Kashuba (@KashubaK), I think the TypeScript team has made their opinion known. While I personally don't agree with the decision to deprioritize exception handling, I understand it. They don't think people will adopt it, and so it's on the community to prove otherwise. (That being said, I'd love if they reconsidered :))
Their reconsideration points made that clear:
Kyle Kashuba (@KashubaK), your steps sound right to me. And your idea of integrating with DefinitelyTyped would be a massive step towards adoption (and maybe the only step needed to satisfy reconsideration point 1).
If you go with the ESLint approach, it looks like eslint-plugin-jsdoc already has the first bullet point (via
requires-throw). And can be a good source of inspiration.As far as the spec of how exactly it should work
@throwsannotation for interopJava seems closest to the hybrid of supporting both (safe and unsafe). But I'd personally advocate for matching swift (checked exceptions), but with the ability to opt into
@throws@throwsKashubaK commentedon Aug 26, 2023
Thanks for your input Kyle Venn (@kvenn)!
The reason I wanted to introduce "safe" exceptions is to address an important note mentioned earlier:
But perhaps it would be more dangerous and confusing than helpful. Some configuration to whitelist certain functions might be appropriate instead.
Regarding an RFC, for something like this I wouldn't hesitate, but I have some ideas that I need to make sure are even feasible before I potentially waste others' time. Regardless I would love a community effort, as it will be required to move the needle in any meaningful direction.
KashubaK commentedon Aug 26, 2023
Personally I agree. That being said, as Kyle Venn (@kvenn) stated, the TS team has already made their stance clear. Robust error handling needs to be normalized in the community before such an invasive new concept is added to countless others' workflows. If our ideas aren't able to proliferate, well, perhaps they were doomed to begin with.
obedm503 commentedon Aug 26, 2023
It would be simpler to just use
// @ts-expect-errorthan introducing a whole new error handling concept.zefir-git commentedon Aug 26, 2023
How would you specify the error type?
obedm503 commentedon Aug 27, 2023
// @ts-expect-errorwould allow you to leave "safe" errors unhandled.KashubaK commentedon Aug 28, 2023
Yeah, but then you lose all other type-checking for that line.
michaelangeloio commentedon Nov 10, 2023
I made an extension similar to this!
Feel free to check it out:
https://marketplace.visualstudio.com/items?itemName=michaelangeloio.does-it-throw-vscode
https://github.com/michaelangeloio/does-it-throw
kvenn commentedon Nov 10, 2023
Very cool! Any plans to build for IntelliJ?
KashubaK commentedon Nov 10, 2023
Michael Angelo Rivera (@michaelangeloio) Super cool, thanks for sharing! I would also love to see a JetBrains plugin implemented.
Any plans/ideas about a CLI? Could it report the class of error? i.e.
class MyCustomError extends Error {}RyanCavanaugh commentedon Nov 10, 2023
Locking for clarity. This issue is concluded and will not be reopened; see #13219 (comment) . For general discussion on the topic, continue if needed at #56365.