Suggestion: `throws` clause and typed catch clause
See original GitHub issueThe 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
.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1494
- Comments:217 (20 by maintainers)
Top GitHub Comments
how is a checked throw different from
Tried<Result, Error>
?instead of
@aleksey-bykov
You’re suggesting not to use
throw
at 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
throws
will 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
throws
clause.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.parse
might throw I need to go to the MDN page and read that:And this is the good case when the error is documented.