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