question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Suggestion: `throws` clause and typed catch clause

See original GitHub issue

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):

  1. When using this function there’s no way to know that it might throw an error
  2. 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:open
  • Created 7 years ago
  • Reactions:1494
  • Comments:217 (20 by maintainers)

github_iconTop GitHub Comments

168reactions
zpdDG4gta8XKpMCdcommented, Jan 9, 2017

how is a checked throw different from Tried<Result, Error>?

type Tried<Result, Error> = Success<Result> | Failure<Error>;
interface Success<Result> { kind: 'result', result: Result } 
interface Failure<Error> { kind: 'failure', error: Error }
function isSuccess(tried: Tried<Result, Error>): tried is Success<Result> {
   return tried.kind === 'result';
}
function mightFail(): Tried<number, string> {
}
const tried = mightFail();
if (isSuccess(tried)) {
    console.log(tried.success);
}  else {
    console.error(tried.error);
}

instead of

try {
    const result: Result = mightFail();
    console.log(success);
} catch (error: Error) {
    console.error(error);
}
141reactions
nitzantomercommented, Jan 10, 2017

@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:

  • This wrapping creates more code
  • It requires that all chain of invoked function return this wrapped value (or error) or alternatively the function that gets Tried<> can not choose to ignore the error.
  • It is not a standard, 3rd party libraries and the native js throw errors

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:

Throws a SyntaxError exception if the string to parse is not valid JSON

And this is the good case when the error is documented.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How do you use typed errors in async catch() - Stack Overflow
We don't allow type annotations on catch clauses because there's really no way to know what type an exception will have. You can...
Read more >
Get a catch block error message with TypeScript - Kent C. Dodds
TypeScript forces you to acknowledge you can't know what was thrown making getting the error message a pain. Here's how you can manage...
Read more >
TypeScript: Narrow types in catch clauses - fettblog.eu
When you are coming from languages like Java, C++, or C#, you are used to doing your error handling by throwing exceptions.
Read more >
Error handling, "try...catch" - The Modern JavaScript Tutorial
Here we use the catch block only to show the message, but we can do much more: send a new network request, suggest...
Read more >
Exceptions, checked; throws clause - CS@Cornell
Here's a suggestion. Don't worry about putting in throws clauses. Put them out of your mind. But, whenever Javatells you that an exception...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Hashnode Post

No results found