Typescript: Suggestion: `throws` clause and typed catch clause

Created on 29 Dec 2016  ·  135Comments  ·  Source: microsoft/TypeScript

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.

Awaiting More Feedback Suggestion

Most helpful comment

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

All 135 comments

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?

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

function fn() {
    throw "error";
}

fn();

// and
try {
    fn();
} finally {
    // do something here
}

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.ts files) and then have the compiler type guard the exception types inside the catch clause.

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);
}

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

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

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

    function mightFail(): Tried<number, string> {
    }
    function mightFailToo(): Tried<number, string> {
        const tried = mightFail();
        if (isSuccess(tried))  { 
             return successFrom(tried.result * 2);
        } else {
             return tried;
        }
    }
    
  • 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:

  • one delivered by the return statement and
  • another delivered by throw

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

consider:

// throw/catch
declare function doThis(): number throws string;
declare function doThat(): number throws string;
function doSomething(): number throws string {
    let oneResult: number | undefined = undefined;
    try {
        oneResult = doThis();
    } catch (e) {
        throw e;
    }

    let anotherResult: number | undefined = undefined;
    try {
        anotherResult = doThat();
    } catch (e) {
        throw e;
    }
    return oneResult + anotherResult;
}

// explicit results
declare function doThis(): Tried<number, string>;
declare function doThat(): Tried<number, string>;
function withBothTried<T, E, R>(one: Tried<T, E>, another: Tried<T, E>, haveBoth: (one: T, another: T) => R): Tried<T, R> {
    return isSuccess(one)
        ? isSuccess(another)
            ? successFrom(haveBoth(one.result, another.result))
            : another
        : one;
}
function add(one: number, another: number) { return one + another; }
function doSomething(): Tried<number, string> {
    return withBothTried(
        doThis(),
        doThat(),
        add
    );
}

@aleksey-bykov

My point with JSON.parse might throwing SyntaxError is 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 SyntaxError with using instanceof.

You can represent the same bad situation with throwing an error.
You can create your own error class which extends Error and 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:

try {
    mightFail();
} catch (e: MyError | string) {
    if (e instanceof MyError) { ... }
    else if (typeof e === "string") { ... }
    else {}
}

Will work as expected in javascript, just without the type annotation.

Using throw is 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.

if we talking about browsers instanceof is only good for stuff that originates from the same window/document, try it:

var child = window.open('about:blank');
console.log(child.Error === window.Error);

so when you do:

try { child.doSomething(); } catch (e) { if (e instanceof SyntaxError) { } }

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

try {
   doSomething(); // <-- uses 3rd party library that by coincidence throws SyntaxError too, but you don' t know it 
} catch (e) {}

besides instanceof is vulnerable to prototype inheritance, so you need to be extra cautions to always check against the final ancestor

class StandardError {}
class CustomError extends StandardError {
}
function doSomething() { throw new CustomError(); }
function oldCode() {
   try {
      doSomething();
   } catch (e) {
      if (e instanceof StandardError) {
          // problem
      }
   }
}

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

Actually, my main concern here is that people will start subclassing Error. I think this is a terrible pattern. More generally, anything that promotes the use of the instanceof operator is just going to create additional confusion around classes.

This is a comment comming from somebody who puts a lot of effort into popularising Haskell and FP as a whole.

i really think this should be pushed harder to the audience, not until it's digested and asked for more can we have better FP support in the language

and it's not as daunting as you think, provided all combinators are written already, just use them to build a data flow, like we do in our project, but i agree that TS could have supported it better: #2319

Monad transformers are a real PITA. You need lifting, hoisting and selective running fairly often. The end result is hardly comprehendible code and much higher than needed barrier of entry. All the combinators and lifting functions (which provide the obligatory boxing/unboxing) are just noise distracting you from the problem at hand. I do believe that being explicit about state, effects, etc is a good thing, but I don't think we have found a convenient wrapping / abstraction yet. Until we find it, supporting traditional programming patterns seems like the way to go without stopping to experiment and explore in the mean time.

PS: I think we need more than custom operators. Higher Kinded Types and some sort of type classes are also essential for a practical monadic library. Among them I'd rate HKT first and type classes a close second. With all that said, I believe TypeScript is not the language for practicing such concepts. Toying around - yes, but its philosophy and roots are fundamentally distant for a proper seamless integration.

Back to the OP question - instanceof is a dangerous operator to use. However explicit exceptions are not limited to Error. You can throw your own ADTs or custom POJO errors as well. The proposed feature can be quite useful and, of course, can also be misused pretty hard. In any case it makes functions more transparent which is undoubtedly a good thing. As a whole I'm 50/50 on it :)

@aleksey-bykov

Developers should be aware of the different js issues you described, after all adding throws to typescript doesn't introduce anything new to js, it only gives typescript as a language the ability to express an existing js behavior.

The fact that 3rd party libraries ca throw errors is exactly my point.
If their definition files were to include that then I will have a way to know it.

@aluanhaddad
Why is it a terrible pattern to extend Error?

@gcnew
As for instanceof, that was just an example, I can always throw regular objects which have different types and then use type guards to differentiate between them.
It will be up to the developer to decide what type of errors he wishes to throw, and it probably is the case already, but currently there's no way to express that, which is what this suggestion wants to solve.

@nitzantomer Subclassing native classes (Error, Array, RegExp, etc) was not supported in older ECMAScript versions (prior to ES6). The down level emit for these classes gives unexpected results (best effort is made but this is as far as one can go) and is the reason for numerous issues logged on daily basis. As a rule of thumb - don't subclass natives unless you are targeting recent ECMAScript versions and really know what you are doing.

@gcnew
Oh, I'm well aware of that as I spent more than a few hours trying to figure out what went wrong.
But with the ability to do so now there shouldn't be a reason not to (when targeting es6).

In anycase this suggestion doesn't assume that the user is subclassing the Error class, it was just an example.

@nitzantomer I'm not arguing that the suggestion is limited to Error. I just explained why it's a bad pattern to subclass it. In my post I actually defended the stance that custom objects or discriminated unions may be used as well.

instanceof is dangerous and considered an anti-pattern even if you take out the specificities of JavaScript - e.g. Beware of instanceof operator. The reason is that the compiler cannot protect you against bugs introduced by new subclasses. Logic using instanceof is fragile and does not follow the open/closed principle, as it expects only a handful of options. Even if a wildcard case is added, new derivates are still likely to cause errors as they may break assumptions made at the time of writing.

For the cases where you want to distinguish among known alternatives TypeScript has Tagged Unions (also called discriminated unions or algebraic data types). The compiler makes sure that all cases are handled which gives you nice guarantees. The downside is that if you want to add a new entry to the type, you'll have to go through all the code discriminating on it and handle the newly added option. The upside is that such code would have most-likely been broken, but would have failed at runtime.

I just gave this proposal a second thought and became against it. The reason is that if throws declarations were present on signatures but were not enforced, they can already be handled by documentation comments. In the case of being enforced, I share the sentiment that they'd become irritating and swallowed fast as JavaScript lacks Java's mechanism for typed catch clauses. Using exceptions (especially as control flow) has never been an established practice as well. All of this leads me to the understanding that checked exceptions bring too little, while better and presently more common ways to represent failure are available (e.g. union return).

@gcnew
This is how it's done in C#, the problem is that docs aren't as standard in typescript.
I do not remember coming across a definition file which is well documented. The different lib.d.ts files do contain comments, but those do not contain thrown errors (with one exception: lib.es6.d.ts has one throws in Date[Symbol.toPrimitive](hint: string)).

Also, this suggestion takes error inferring into account, something that won't happen if errors are coming from documentation comments. With inferred checked exceptions the developer won't even need to specify the throws clause, the compiler will infer it automatically and will use it for compilation and will add it to the resulting definition file.

I agree that enforcing error handling isn't a good thing, but having this feature will just add more information which can be then used by those who wish to.
The problem with:

... there are better and presently more common ways to represent failure

Is that there's no standard way of doing it.
You might use union return, @aleksey-bykov will use Tried<>, and a developer of another 3rd party library will do something completely different.
Throwing errors is a standard across languages (js, java, c#...) and as it's part of the system and not a workaround, it should (in my opinion) have better handling in typescript, and a proof of that is the number of issues I've seen here over time which ask for type annotation in the catch clause.

I would love to have information in the tooltip in VS if a function (or called function) can throw. For *.d.ts files we probably need a fake parameter like this since TS2.0.

@HolgerJeromin
Why would it be needed?

here is a simple question, what signature should be inferred for dontCare in the code below?

function mightThrow(): void throws string {
   if (Math.random() > 0.5) {
       throw 'hey!';
   }
}

function dontCare() {
   return mightThrow();
}

according to what you said in your proposal it should be

function dontCare(): void throws string {

i say it should be a type error since a checked exception wasn't properly handled

function dontCare() { // <-- Checked exception wasn't handled.
         ^^^^^^^^^^

why is that?

because otherwise there is a very good chance of getting the state of the immediate caller corrupt:

class MyClass {
    private values: number[] = [];

    keepAllValues(values: number[]) {
       for (let index = 0; index < values.length; index ++) {
            this.values.push(values[index]); 
            mightThrow();
       }
    }
}

if you let an exception to slip through you can not infer it as checked, because the behavior contract of keepAllValues would be violated this way (not all values were kept despite the original intent)

the only safe way to is catch them immediately and rethrow them explicitly

    keepAllValues(values: number[]) {
           for (let index = 0; index < values.length; index ++) {
                this.values.push(values[index]); 
                try {
                    mightThrow();
                } catch (e) {
                    // the state of MyClass is going to be corrupt anyway
                    // but unlike the other example this is a deliberate choice
                    throw e;
                }
           }
    }

otherwise despite the callers know what can be trown you can't give them guarantees that it's safe to proceed using code that just threw

so there is no such thing as automatic checked exception contract propagation

and correct me if i am wrong, this is exactly what Java does, which you mentioned as an example earlier

@aleksey-bykov
This:

function mightThrow(): void {
   if (Math.random() > 0.5) {
       throw 'hey!';
   }
}

function dontCare() {
   return mightThrow();
}

Means that both mightThrow and dontCare are inferred to throws string, however:

function dontCare() {
    try {
        return mightThrow();
    } catch (e: string) {
        // do something
    }
}

Won't have a throw clause because the error was handled.
This:

function mightThrow(): void throws string | MyErrorType { ... }

function dontCare() {
    try {
        return mightThrow();
    } catch (e: string | MyErrorType) {
        if (typeof e === "string") {
            // do something
        } else { throw e }
    }
}

Will have throws MyErrorType.

As for your keepAllValues example, I'm not sure what you mean, in your example:

class MyClass {
    private values: number[] = [];

    keepAllValues(values: number[]) {
       for (let index = 0; index < values.length; index ++) {
            this.values.push(values[index]); 
            mightThrow();
       }
    }
}

MyClass.keepAllValues will be inferred as throws string because mightThrow might throw a string and that error was not handled.

As for your keepAllValues example, I'm not sure what you mean

I meant the exceptions coming unhandled from mightThrow interrupt keepAllValues and make it finish in a middle of what it was doing leaving its state corrupt. It is a problem. What you suggest is to close your eyes on this problem and pretend it's not serious. What I suggest is to address this problem by requiring that all checked exceptions are immediately handled and explicitly rethrown. This way there is no way to get the state corrupt unintentionally. And although it can still be corrupt if you choose so, it would require some deliberate coding.

Think about it, there are 2 ways we can go about exceptions:

  • unhandle them, which leads to a crash, if the crash is what you want then we are fine here
  • if you don't want a crash then you need some guidance as to what sort of exception you need to be looking for, and this is where your proposal comes in: checked exeptions - all explicitly listed, so you can handle them all and don't miss anything

now if we decided to go with the checked exceptions which are properly handled and prevent a crash we need rule out a situation when we handle an exception coming from several layers deep of where you are catching it:

export function calculateFormula(input) {
    return calculateSubFormula(input);
}
export function calculateSubFormula(input) {
   return calculateSubSubFormula(input);
}
export function calculateSubSubFormula(input): number throws DivisionByZero  {
   return 1/input;
}

try {
   calculateFormula(0);
} catch (e: DivisionByZero) {
   // it doesn't make sense to expose DivisionByZero from under several layers of calculations
   // to the top level where nothing we can do or even know what to do about it
   // basically we cannot recover from it, because it happened outside of our immediate reach that we can control
}

the example above brings a interesting case for consideration, what would be the inferred signature of:

function boom(value: number) /* what comes here?*/  {
    return 1/value;
}

another interesting case

// 1.
function run<R, E>(callback(): R throws E) /* what comes here? */ {
    try {
        return callback();
    } catch (e: DivisionByZero) {
        // ignore
    }
}

function throw() { return 1 / 0; }

// 2.
run(throw); /* what do we expect here? */


@aleksey-bykov
So you propose that all errors must be handled like it is with java?
I'm not a fan of that (even though I come from java and still loving it) because js/ts are way more dynamic and their users are accustomed to that.
It can be a flag that makes you deal with errors if you include it when compiling (like strictNullChecks).

My suggestion isn't here to solve unhandled exceptions, the code you posted will break now without this feature implemented, and it would break in js as well.
My suggestion just let you as a developer be more aware of the different errors that might be thrown, it's still up to you if to handle them or ignore them.

As for the division by 0 issue, it doesn't result in an error:

console.log(1 / 0) // Infinity
console.log(1 / "hey!") // NaN

more aware of the different errors that might be thrown

there is no point of doing so unless they can deal with them, the current proposal isn't viable because of the cases i listed

So you propose that all errors must be handled like it is with java?

yes, this is what it means having checked exceptions

@aleksey-bykov
I don't see why any of the cases you listed render this proposal as inviable.

There's no problem with handling an error that was thrown way down the invocation chain, even if I'm using a function that was inferred of throwing DivisionByZero (regardless of where it was thrown), I can choose to handle it.
I can try to re-try it with different arguments, I can show the user a message that something went wrong, I can log this problem so that I can later change my code to handle it (if it happens often).

Again, this proposal doesn't change anything in runtime, so everything that worked will continue to work as before.
The only difference is that I will have more information about the errors that might be thrown.

i see what you are saying, nothing is going to be changed at javascript runtime, however your message here is to give users some illusion that they know what they are doing by handling an exception that came from 20 layers down below with the same confidence as they would handle an immediate exception

there is simply no way they can fix a problem that happened 20 layers down below

you can log it, sure, just as any unchecked exception, but you cannot fix it

so it's a lie generally speaking, there is enough lies in TS, let's not confuse people even more

@aleksey-bykov
What you're describing exists in all languages that support exceptions.
No one said that catching an exception will fix the problem, but it will let you handle it gracefully.

Knowing which errors can be thrown when invoking a function will help the developers to separate between the errors that they can handle and those which they can't.

Right now developers might not know that using JSON.parse might throw an error, but if it was part of the lib.d.ts and the IDE would let him know (for example) then maybe he'll choose to handle this case.

you can't handle a problem happened 20 layers below gracefully, because the internal state is corrupt in 19 layers and you can't go there because the state is private

to be constructive: what i am suggesting is to require users handle checked exceptions immediately and rethrow them explicitly, this way we rule out unintended confusion and separate checked exceptions from unchecked:

  • checked exception: happened in the immediate reach and must be handled, this way it's guaranteed that the state isn't corrupt and it's safe to proceed
  • unchecked exception: happened in the immediate reach or much further down, it cannot be handled because the state was corrupt, you can log it or proceed on your own risk

SyntaxError in JSON.parse should be declared as a checked exception

@aleksey-bykov

I don't see why there's a need to enforce developers to do something they don't wish to, something that they haven't done so far.

Here's an example:
I have a web client in which the user can write/paste json data, then click a button.
The app takes this input and passes it to a 3rd party library that somehow parses this json and returns the json along with the different types of the values (string, number, boolean, array, etc).
If this 3rd party library throws a SyntaxError I can recover: inform the user that his input is invalid and he should try again.

By knowing which errors can be thrown when invoking a function the developer can decide what he can/wishes to handle and what not.
It shouldn't matter how deep in the chain the error was thrown.

look you don't seem to get what i am saying, we are going in circles

by letting SyntaxError thrown from 3rd party library you are exposing your user to the implementation details of your own code which are supposed to be encapsulated

basically you are saying, hey, it's not my code that doesn't work, it's that stupid library that i found on the internet and used, so if you have a problem with it, deal with that 3rd party lib, not me, i just said what i was asked to

and there is no guarantee that you can still use the instance of that 3rd lib after that SyntaxError, it's your responsibility to provide guarantees to the user, say by reinstantiating the 3rd party control after it threw

bottom line, you need to be in charge for handling inner exceptions (not all of them, only the checked ones, i beg you)

I am getting what you're saying, but I don't agree with it.
You're right, that is basically what I'm saying.
If I used a 3rd party library that throws an error I can choose to deal with it or ignore it and let the user of my code handle it.
There are many reasons to do so, for example the lib that I'm writing is UI-agnostic, so I can't inform the user that something is wrong, but who ever uses my lib can handle the errors that are thrown when using my lib and handle them by interacting with the user.

If a library is left with a corrupted state when it throws, then it probably needs to document it.
If I then use such a library and as a result in an error in it my state becomes corrupted then I need to document it.

Bottom line:
This suggestion comes to offer more information about thrown errors.
It shouldn't enforce developers to do things differently, just make it easier on them to deal with the errors if they choose to.

you can disagree, it's fine, let's just not call them checked exceptions please, because the way you put it isn't what checked exceptions are

let's call them listed or revealed exceptions, because all you care is to make developers aware of them

@aleksey-bykov
Fair enough, name changed.

@aleksey-bykov

you can't handle a problem happened 20 layers below gracefully, because the internal state is corrupt in 19 layers and you can't go there because the state is private

No you cannot fix the internal state, but could certainly fix the local state, and that's exactly the point of handling it here and not deeper in the stack.

If your argument is that there is no way to be certain what state some shared mutable values are in when handling the exception, then it's an argument against imperative programming, and not confined to this proposal.

if every layer is bound to take responsibility of reacting to an exception coming immediately from a layer below, there is a much better chance for a successful recovery, this is the idea behind the checked exceptions as i see it

to put it in different words, exceptions coming from more than 1 level below is a sentence, it's too late to do anything other than re-instantiating all infrastructure from ground up (if you are lucky enough there are no global leftovers that you can't reach)

proposal as stated is mostly useless, because there is no reliable way to react to the knowledge of something bad happened outside of your reach

This is great. FWIW: I think if added, it should be required by default to handle throwing methods or mark your method as throwing as well. Otherwise it's just documentation pretty much.

@agonzalezjr
I think that like most features in typescript you should be able to opt-in with this feature as well.
Just like it's not mandatory to add types, it shouldn't be a must to throw/catch.

There probably should be be a flag to make it a must, like --onlyCheckedExceptions.

In any case, this feature will also be used to infer/validate the types of thrown exceptions, so not just for documentation.

@nitzantomer

Here's an example:
I have a web client in which the user can write/paste json data, then click a button.
The app takes this input and passes it to a 3rd party library that somehow parses this json and returns the json along with the different types of the values (string, number, boolean, array, etc).
If this 3rd party library throws a SyntaxError I can recover: inform the user that his input is invalid and he should try again.

This is certainly one area where the whole idea of checked exceptions becomes murky. It is also where the definition of _exceptional situation_ becomes unclear.
The program in your example would be an argument for JSON.parse being declared as throwing a checked exception.
But what if the program is an HTTP client and is calling JSON.parse based on the value of a header attached to an HTTP response that happens to contain an ill-formed body? There is nothing meaningful the program can do to recover, all it can do is rethrow.
I would say that this is an argument against JSON.parse being declared as checked.

It all depends on the use case.

I understand that you are proposing that this be under a flag but let's imagine that I want to use this feature so I have enabled the flag. Depending on what kind of program I am writing, it may either help or hinder me.

Even the classic java.io.FileNotFoundException is an example of this. It is checked but can the program recover? It really depends on what the missing file means to the caller, not the callee.

@aluanhaddad

This suggestion doesn't propose to add any new functionality, only to add a way to express in typescript something that already exists in javascript.
Errors are thrown, but currently typescript has no way of declaring them (when throwing or catching).

As for your example, by catching the error the program can fail "gracefully" (for example showing the user a "something went wrong" message) by catching this error, or it can ignore it, depending on the program/developer.
If the programs' state can be affected by this error, then handling it can keep a valid state instead of a broken one.

In any case, the developer should make the call of whether he can recover from a thrown error or not.
It's also up to him to decide what it means to recover, for example if I'm writing this http client to be used as a 3rd party library, I might want all errors thrown from my library to be of the same type:

enum ErrorCode {
    IllFormedJsonResponse,
    ...
}
...
{
    code: ErrorCode;
    message: string;
}

Now, in my library when I parse the response using JSON.parse I want to catch a thrown error and then throw my own error:

{
    code: ErrorCode.IllFormedJsonResponse,
    message: "Failed parsing response"
} 

If this feature is implemented then it will be easy for me to declare this behavior and it will be clear to the users of my library how it works and fails.

This suggestion doesn't propose to add any new functionality, only to add a way to express in typescript something that already exists in javascript.

I know. I'm talking about the errors that TypeScript would emit under this proposal.
My assumption was that this proposal implied a distinction between checked and unchecked exception specifiers (inferred or explicit), again only for type checking purposes.

@aluanhaddad

What you said in the previous comment:

But what if the program is an HTTP client and is calling JSON.parse based on the value of a header attached to an HTTP response that happens to contain an ill-formed body? There is nothing meaningful the program can do to recover, all it can do is rethrow.

Applies the same for returning a null when my function is declared to return a result.
If the developer chooses to use strictNullChecks then you can say the exact same thing if the function returns a null (instead of throwing) then in the same scenario "there is nothing meaningful the program can do to recover".

But even without using a onlyCheckedExceptions flag this feature is still useful because the compiler will complain for example if I try to catch the error as a string when the function is declared to throw only Error.

Nice Idea, it would be helpful but not strict/type safe since there is no way of knowing what nested calls might throw at you.

Meaning, if I have a function that might throw an exception of type A, but inside I call a nested function and don't put it in try catch - its going to throw its type B exception to my caller.
So, if the caller expects only type A exceptions there is no guarantee he won't get other types from nested exceptions.

(the thread is too long so - sorry if i missed this comment)

@shaipetel
The proposition states that the compiler will infer the types of unhandled errors and will add them to the function/method signature.
So in the case you described your function will throw A | B in case B was not handled.

Oh, I see. It will drill down through all methods I call and collect all possible exception types?
I would love to see it happen, if it is possible. See, a developer can always have an unexpected exception that won't be declared, in which case an "object not set to instance" or "divide by 0" or similar exceptions are always possible almost from any function.
IMHO, it would have been best handled like in C# where all exceptions inherit from a base class that has a message and not to allow at all the throw unwrapped text or other objects. If you have base class and inheritance you can cascade your catches and handle your expected error in one block, and other unexpected in another.

@shaipetel
In javascript all errors are based on the Error class, but you're not restricted to throwing errors, you can throw anything:

  • throw "something went wrong"
  • throw 0
  • throw { message: "something went wrong", code: 4 }

Yeah, I know how JavaScript works, we are discussing TypeScript which emposes further limitations.
I suggested, a good solution IMHO would be the make TypeScript follow the exception handling that requires all thrown exceptions to be of a specific base class and not to allow throwing unwrapped values directly.

So, it won't allow "throw 0" or "throw 'some error'".
Just like JavaScript allows many things TypeScript doesn't.

Thanks,

@nitzantomer

Applies the same for returning a null when my function is declared to return a result.
If the developer chooses to use strictNullChecks then you can say the exact same thing if the function returns a null (instead of throwing) then in the same scenario "there is nothing meaningful the program can do to recover".

But even without using a onlyCheckedExceptions flag this feature is still useful because the compiler will complain for example if I try to catch the error as a string when the function is declared to throw only Error.

I see what you're saying, that makes sense.

@shaipetel as previously discussed in this proposal and elsewhere, subclassing built in functions like Error and Array does not work. It leads to behavior that is different across widely used run times and compilation targets.

Catching values of various non-error types is the most viable way two envision the proposed functionality. I actually don't see that as a problem as exception propagation mechanisms taking advantage of this feature would likely result in errors that are much more specific, and much more helpful, than say a stack trace or other error specific properties.
Extending Error is not viable. It will not be viable until all target's below es2015 are no longer in use.

If this proposal leads indirectly to more subclassing of the Error function then I think it is a bad idea. Such an objection is completely separate from any philosophical objections around the use of exceptions for control flow or the definition of exceptional circumstance. Therefore if this proposal were adopted I would expect extremely loud and in-your-face documentation regarding correct use and the necessity of avoiding subclassing Error.

I would love for there to be some sort of helper logic for handling typed exceptions, I am refactoring a lot of promise code over to use async/await, which currently looks like:

doSomethingWhichReturnsPromise()
    .then(send(200))
    .catch(NotFoundError, (error) => { send(404); })
    .catch(SomeBadDataError, (error) => { send(400); })
    .catch(CantSeeThisError, (error) => { send(403); })
    .catch((error) => { send(500); })

Then in the new world it looks like this:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch(error)
{
    if(error instanceof NotFoundError) { send(404); } 
    else if(error instanceof SomeBadDataError) { send(400); } 
    else if(error instanceof CantSeeThisError) { send(403); } 
    else { send(500); } 
}

Which is ok but requires more code and is slightly less readable in some ways, so it would be great if there was some form of support for:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch(NotFoundError, error) { send(404); }
catch(SomeBadDataError, error) { send(404); }
catch(CantSeeThisError, error) { send(404); }
catch(error) { send(404); }

Which would output the previous bit, but as syntactic sugar, you could even do it as generics but its slightly nastier:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch<NotFoundError>(error) { send(404); }
catch<SomeBadDataError>(error) { send(404); }
catch<CantSeeThisError>(error) { send(404); }
catch(error) { send(404); }

@grofit
while I'd like it if typescript would support what you're suggesting, it is not related to this issue in my opinion.
What you're suggesting can even be implemented without implementing what this issue is about, just that the compiler won't be able to complain (for example) that NotFoundError isn't thrown.

I thought this was a suggestion for typed catches, not an issue of any kind, I just didnt want to duplicate threads, will go post it in its own issue then.

@grofit
Typed catches are also part of the feature request, but also the ability to inform the compiler what type of errors can be thrown from functions (and then the compiler will also be able to infer what are the types of errors that can be thrown).

In my opinion typed catches can be implemented without the other parts. Open up a new issue, maybe the TS team will decide to mark it as duplicate, I don't know.

@grofit

Which would output the previous bit, but as syntactic sugar, you could even do it as generics but its slightly nastier:

try
{
  await doSomethingWhichReturnsPromise();
  send(200);
}
catch<NotFoundError>(error) { send(404); }
catch<SomeBadDataError>(error) { send(404); }
catch<CantSeeThisError>(error) { send(404); }
catch(error) { send(404); }

is not going to work because it implies generation of code based only on types whereas

 doSomethingWhichReturnsPromise()
    .then(send(200))
    .catch(NotFoundError, (error) => { send(404); })
    .catch(SomeBadDataError, (error) => { send(400); })
    .catch(CantSeeThisError, (error) => { send(403); })
    .catch((error) => { send(500); })

passes the Error function in each case and is likely implemented with instanceof. Code like that is toxic since it encourages extending Error which is a very bad thing.

@nitzantomer I agree that it is a separate issue. The OP has examples of catching types that do not extend Error.

@aluanhaddad
For what @grofit asks for you can then do something like:

try {
    // do something
} catch (isNotFoundError(error)) {
    ...
}  catch (isSomeBadDataError(error)) {
    ...
} catch (error) {
    ...
}

Where the isXXX(error) are type guards of the form of:
function isXXX(error): error is XXX { ... }

@nitzantomer sure, but type guards are not the issue. The issue is

class MyError extends Error {
  myErrorInfo: string;
}

which is problematic, but has already been discussed here. I do not wish to belabor the point, but many large and well known code bases have been negatively impacted by adopting this poor practice. Extending Error is a bad idea.

@aluanhaddad
I'm aware of that, which is why I offered a way to accomplish what @grofit requested but without the need to extend Error. A developer will still be able to extend Error if he wants to, but the compiler will be able to generate js code which doesn't need to use instanceof

@nitzantomer I realize you are aware, but I didn't realize what you were suggesting to @grofit which sounds like a nice idea.

I'm just beating a dead horse because I dislike having to deal with APIs that want to make me use such patterns. Regardless, I'm sorry if I took this discussion off topic.

Has there been any more thought into this discussion? I would love to see a throws clause in typescript.

@aluanhaddad You keep saying that extending Error is bad practice. Can you elaborate a bit on this?

It's been discussed at length. Basically you can't reliably extend built-in types. The behavior varies dramatically across the combination of environments and --target settings.

The only reason I asked is because your comments were the first I heard of this. After googling a bit, I only found articles explaining how and why should _should_ extend Error.

No it will not work. You can read about it in detail in https://github.com/Microsoft/TypeScript/issues/12123 and the numerous linked issues.

It will work, but only in "specific environments", which are becoming the majority as ES6 is being adapted.

@nitzantomer: Do you think it's possible to implement a TSLint check which informs the developer when a function with @throws is called without a surrounding try/catch?

@bennyn I'm a TSLint user, but I've never looked into making new rules.
I really don't know if it's possible (though I guess that it is), and if so how easy.

If you do give it a go, please update here, thanks.

@shaipetel (and also @nitzantomer)

Re:

Yeah, I know how JavaScript works, we are discussing TypeScript which emposes further limitations.
I suggested, a good solution IMHO would be the make TypeScript follow the exception handling that requires all thrown exceptions to be of a specific base class and not to allow throwing unwrapped values directly.
So, it won't allow "throw 0" or "throw 'some error'".
Just like JavaScript allows many things TypeScript doesn't.

Not sure if this is what you were suggesting @shaipetel but just in case... I'd caution against having Typescript restrict throw to only returning Errors. React's upcoming Async Rendering features work under the hood by throwing Promises! (Sounds strange I know... but I've read they have evaluated this versus async/await and yield/yield* generators for their use case and I'm sure know what they're doing!)

throwing Errors is I'm sure 99% of use cases out there, but not 100%. I don't think Typescript should restrict users to just throwing things that extend anError base class. It's certainly much more advanced, but throw has other use cases than errors/exceptions.

@mikeleonard
I agree, there are many examples of existing js code that throws a variety of types (I've seen a lot of throw "error message string").
The new React feature is another good example.

Just my two cents, I'm converting code to async/await and so I'm being subjected to throw (as an aside, I hate exceptions). Having a throws clause as this issue discusses would be nice in my opinion. I think it would be useful too even if "throws any" was allowed. (Also, maybe a "nothrows" and compiler option that defaults things to "nothrows".)

It seems like a natural extension to allow typing what a function throws. Return values can be optionally typed in TS, and to me it feels like throw is just another type of return (as evidenced by all the alternatives suggested to avoid throw, like https://stackoverflow.com/a/39209039/162530).

(Personally, I'd also love to have the (optional) compiler option to enforce that any caller to a function declared as throws must either also declare as throws or catch them.)

My current use case: I don't want to convert my entire [Angular] code base to use exceptions for error handling (seeing as I hate them). I use async/await in the implementation details of my APIs, but convert throw to normal Promises/Observables when an API returns. It would be nice to have the compiler check that I'm catching the right things (or catching them at all, ideally).

@aleksey-bykov Let's not change the nature of JavaScript, let's just adding typing to it. :)

adding typing already changes it (cuts out code that doesnt make sense),
same way we can tighten up exception handling

On Thu, Jul 26, 2018, 8:22 PM Joe Pea notifications@github.com wrote:

@aleksey-bykov https://github.com/aleksey-bykov Let's not change the
nature of JavaScript, let's just adding typing to it. :)


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/13219#issuecomment-408274156,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AA5PzfUNS5E093Z74WA4WCUaTyWRRZC3ks5uKl1FgaJpZM4LXwLC
.

For promises, if you use then chaining instead of async/await and avoid using throw, it all works quite beautifully actually. Here is a proof of concept sketch:

https://bit.ly/2NQZD8i - playground link

interface SafePromise<T, E> {
    then<U, E2>(
        f: (t: T) => SafePromise<U, E2>):
        SafePromise<U, E | E2>;

    catch<U, E2>(
        f: (e: E) => SafePromise<U, E2>):
        SafePromise<U, E2>

    catch<U, E1, E2>(
        guard: (e: any) => e is E1,
        f: (e: E) => SafePromise<U, E2>):
        SafePromise<U, Exclude<E, E1> | E2>
}

declare function resolve<T>(t:T): SafePromise<T, never>
declare function reject<E extends Error>(e:E): SafePromise<never, E>


class E404 extends Error {
    code:404 = 404;
}

class E403 extends Error {
    code:403 = 403;
    static check(e: any): e is E403 { return e && e.code === 403 }
}

let p = resolve(20)


let oneError = p.then(function f(x) {
    if (x > 5) return reject(new E403())
    return resolve(x)
})


let secondError = oneError.then(x => {
    if (x > 10) return reject(new E404())
    return resolve(x)
})

let remove403 = secondError.catch(E403.check, e => {
    return resolve(25)
})

let moreErrorsInfer = p.then(x => {
    if (x > 5) return reject(new E403())
    if (x > 10) return reject(new E404())
    return resolve(x)
})

let moreErrorsNoInfer = p.then((x):SafePromise<number, E403|E404> => {
    if (x > 5) return reject(new E403())
    if (x > 10) return reject(new E404())
    return resolve(x)
})

The predicate catch removes types from the union, while returning a reject() adds to them. Only thing that doesn't quite work is the inference of union-parameter types (probably a bug)

Problems addressed in the example above:

  • its not restricted to errors, you can reject with anything (although probably a bad idea)
  • its not restricted to using instanceof, you can use any predicate type guard to remove errors from the union

The main issue I see using this for all code are callback signatures, to make it work in a compatible way the default "return type" would be "might-throw-any", and if you want to restrict that you would say throws X or throws never if it doesn't e.g.

declare function pureMap<T>(t:T[], f: (x:T) => U throws never):U[] throws never

The version without the signature:

declare function dirtyMap<T>(t:T[], f: (x:T) => U):U[]

would actually default to

declare function dirtyMap<T>(t:T[], f: (x:T) => U throws any):U[] throws any

to guarantee that all current code compiles.

Unlike with strictNullChecks, where null return values are quite uncommon, I think exceptions in JS are quite pervasive. Modelling them in .d.ts files might not be too bad (import types from dependencies to describe your errors), but it will definitely be a non trivial effort and will result with huge unions.

I think a good middle ground would be to focus on Promises and async/await, as a Promise is already a wrapper, and async code is where error handling branches the most in typical scenarios. Other errors would be "unchecked"

@spion-h4 does this (already/will) can use on typescript?

declare function dirtyMap<T>(t:T[], f: (x:T) => U throws any):U[] throws any

@bluelovers
no, typescript doesn't currently support the throws keyword, this is what this issue is all about.

Something to note (not that it necessarily blocks this proposal) is that without nominal classes typing builtin errors still isn't particularly useful as they're all structurally the same.

e.g.:

class AnotherError extends Error {}

function* range(min: number, max: number): Iterable<number> throws TypeError, RangeError {
  if (typeof min !== 'number') {
    throw new TypeError('min must be a number')
  }
  if (typeof min !== 'number') {
    throw new TypeError('max must be a number')
  }
  if (!Number.isSafeInteger(min)) {
    // Allowed because without nominal types we can't distinguish
    // Error/RangeError/TypeError/etc
    throw new Error('min must be a safe integer')
  }
  if (!Number.isSafeInteger(max)) {
    // Also allowed because AnotherError is also structurally
    // compatible with TypeError/RangeError
    throw new AnotherError('max must be a safe integer')
  }
  for (let i = min; i < max; i++) {
    yield i
  }
}

@Jamesernator
This issue has nothing to do with the proposal.
You can get into the same exact problem doing this:

class BaseClass {
    propA!: string;
}

class MyClass1 extends BaseClass { }

class MyClass2 extends BaseClass { }

function fn(): MyClass1 {
    return new MyClass2();
}

It's a language limitation which effects a lot of use cases.

There is a great read by @ahejlsberg on typed exceptions: https://www.artima.com/intv/handcuffs.html

I believe TypeScript is in a good position to avoid these issues. TypeScript is all about _pragmatic_ solutions to _real-world_ problems. In my experience, in big JavaScript and TypeScript codebases, error handling is one of the biggest problems - seeing which errors functions _might_ throw and I _might_ want to handle is incredibly hard. It is only possible through reading & writing good documentation (we all know how good we are at this /s) or looking at the implementation, and since in opposite to return values, exceptions just propagate through function calls automatically, it's not enough to just check the directly called function, but to catch 'em all, you would have to check nested function calls too. _This is a real-world problem_.

Errors in JavaScript are actually very useful values. Many APIs in NodeJS throw detailed error objects, that have well-defined error codes and expose useful metadata. For example, errors from child_process.execFile() have properties like exitCode and stderr, errors from fs.readFile() have error codes like ENOENT (file not found) or EPERM (insufficient permissions). I know many libraries that do this too, e.g. the pg database driver gives you enough metadata on an error to know which exact column constraint caused an INSERT to fail.

You can see a worrying amount of brittle regex checks on error messages in codebases because people are not aware that errors have proper error codes and what they are.

If we could define in @types declarations and lib.d.ts what errors these functions can throw, TypeScript would have the power to help us with the structure of the error - what possible errors there can be, what the error code values are, what properties they have. This is _not_ about typed exceptions and therefor completely avoids all the issues with typed exceptions. It is a _pragmatic solution_ to a _real-world problem_ (in opposite to telling people to use monadic error return values instead - the reality of JavaScript land looks different, functions throw errors).

The annotation can be completely optional (or made required with a compiler flag). If not specified in a declaration file, a function simply throws any (or unknown).
A function can manually declare to never throw with throws never.
If the implementation is available, a function throws a union of all the exception types of the functions it calls into and its own throw statements that are not inside a try block with a catch clause.
If one of them throws any, the function throws any too - this is okay, at every function boundary the dev has the opportunity to correct it through an explicit annotation.
And in many many cases, where a single well-known function is called and wrapped in a try/catch (e.g. reading a file and handling it not being found), TypeScript can then infer the type in the catch clause.

We don't need Error subclassing for this nor instanceof - error types can be interfaces, that specify error codes with string literals, and TypeScript can discriminate the union on the error code, or use type guards.

Defining error types

interface ExecError extends Error {
  status: number
  stderr: Buffer
}
function execFileSync(cmd: string): Buffer throws ExecError;
interface NoEntityError extends Error { code: 'ENOENT' }
interface PermissionError extends Error { code: 'EPERM' }
function readFileSync(file: string): Buffer throws NoEntityError | PermissionError;



md5-818797fe8809b5d8696f479ce1db4511



Preventing a runtime error due to type mismatch



md5-c2d214f4f8ecd267a9c9252f452d6588



Catching errors with type switch



md5-75d750bbe0c3494376581eaa3fa62ce5



```ts
try {
  const resp = await fetch(url, { signal })
  if (!resp.ok) {
    // inferred error type
    // look ma, no Error subclassing!
    throw Object.assign(new Error(resp.statusText), { name: 'ResponseError', response })
  }
  const data = await resp.json()
} catch (err) { // AbortError | Error & { name: 'ResponseError', response: Response } | SyntaxError
  switch (err.name)
    case 'AbortError': return; // Don't show AbortErrors
    default: displayError(err); return;
  }
}



md5-a859955ab2c42d8ce6aeedfbb6443e93



```ts
interface HttpError extends Error { status: number }
// Type-safe alternative to express-style middleware request patching - just call it (TM)
// The default express error handler recognises the status property
function checkAuth(req: Request): User throws HttpError {
    const header = req.headers.get('Authorization')
    if (!header) {
        throw Object.assign(new Error('No Authorization header'), { status: 401 })
    }
    try {
        return parseHeader(header)
    } catch (err) {
        throw Object.assign(new Error('Invalid Authorization header'), { status: 401 })
    }
}

Aside from errors, is it also possible to mark other kinds of side-effects like

  • Divergence (infinite loop / not returning)
  • IO
  • Non-determinism (Math.random)

It reminds me Koka from MSR which can tag effects on returning types.

The proposal:

function square(x: number): number &! never { // This function is pure
  return x * x
}

function square2(x : number) : number &! IO {
  console.log("a not so secret side-effect")
  return x * x
}

function square3( x : number ) : number &! Divergence {
  square3(x)
  return x * x
}

function square4( x : number ) : number &! Throws<string> { // Or maybe a simple `number &! Throws`?
  throw "oops"
  return x * x
}

function map<T, R, E>(a: T[], f :(item: T) => R &! E): R[] &! E { ... }
function map<T, R>(a: T[], f :(item: T) => R): R[] { ... } // will also work; TS would collect side effects

function str<T>(x: T): string &! (T.toString.!) // care about the side effect of a type

I love the idea of declaration of error type! This would a big help for people who need it and won't do anything bad for people who don't like it. I have now the problem in my node project, which could be solved more faster with this feature. I have to catch errors and send proper http code back - to do that I have to always check all functions calls to find out exceptions I have to handle - and this is no fun -_-

PS. The syntax in https://github.com/Microsoft/TypeScript/issues/13219#issuecomment-428696412 looks very good - &! and wrapping error types in <> just amazing.

I'm also in favor of this. Now all errors are untyped and that's pretty annoying. Although I hope most throws can be derived from the code iso us having to write it everywhere.

This way people can even write tslint rules that forces the developer to catch user unfriendly errors on rest endpoints etc.

I was actually surprised that this functionality wasn't _already_ inside of TypeScript. One of the first things I went to declare. It would be okay if the compiler does or doesn't enforce it, as long as you can get the information that there will be a thrown error, and what type of errors will be thrown.

Even if we don't get the &! and just get the Throws<T> that would be :+1:

This is exactly what I have in mind, it would be so much more beautiful if TypeScript supports these clauses.

Another possible feature would be to somehow force (perhaps at the function level, or at the module level) strictExceptionTracking - which would mean you'd have to invoke anything declared as throws X with an expression try! invokeSomething() like in Rust and swift.

It will simply compile to invokeSomething() but the possibility of exceptions will be visible in the code, making it easier to spot if you're leaving something mutable in a bad transient state (or leaving allocated resources undisposed)

@be5invis

function square(x: number): number &! never { // This function is pure
  return x * x
}
...

I'm just giving my two cents here but the &! looks incredibly ugly to me.
I think people that are new to typescript would be kind of throw off by this symbol, it's really uninviting.
A simple throws is more explicit, intuitive and simple, in my opinion.

Other than that, +1 for typed exceptions. 👍

I'd like to add in my +1 for typed and checked exceptions.
I've made great use of typed exceptions in my code. I'm fully aware of the pitfalls of instanceof, I've actually used it to my advantage by being able to write generic handlers for related errors that inherit from a common base class. Most other methods I've encountered to avoid inheritance from the base Error class end up being ( at least ) equally complex and problematic in different ways.
Checked exceptions are an improvement that in my opinion lends itself to greater static analysis insight into the codebase. In a codebase of sufficient complexity it might be easy to miss what exceptions are thrown by a particular function.

For those that are looking for compile-time error safety in typescript you can use my ts-results library.

@vultix for me unfortunately, the separation isn't clear enough to put this into a team setting.

@vultix The approach of your lib has been discussed above, and it's the exact opposite of what this feature is here to solve.
Javascript already has the mechanism for handling errors, it's called exceptions, but typescript lacks a way of describing it.

@nitzantomer I completely agree that this feature is a necessity for typescript. My library is nothing more than temporary stand-in until this feature is added.

function* range(min: number, max: number): Iterable<number> throws TypeError, RangeError {

I know I am a little late to this game on this, but the below seems a little more Typescripty syntax rather than comma.

Iterable<number> throws TypeError | RangeError

But I am still good with comma.. I just wish we had this in the language.
The main one is I would like it on JSON.parse() because a lot of my co-workers seem to forget that JSON.parse can thrown an error and it would save a lot of back-and-forth with pull requests.

@WORMSS
I completely agree.
My proposal included the syntax you recommend:

function mightThrow(): void throws string | MyErrorType { ... }

Can we follow the error declaration pattern of an OOP language like Java? "throws" keyword is used to declare that we need to "try..catch" when using a function with a potential error

@allicanseenow
The proposal is for optional usage of the throws clause.
That is, you won't have to use try/catch if you're using a function that throws.

@allicanseenow you may want to read my writeup above for context, including the linked article: https://github.com/microsoft/TypeScript/issues/13219#issuecomment-416001890

I think this is one of THE missing things in typescript type system.
Its purely optional, doesnt change emitted code, helps working with libraries and native functions.

Also knowing what errors might be thrown could allow editors to "autocomplete" catch clause with if conditions to handle all errors.

I mean, really even small application deserves proper error handling - the worst thing now is, that users don't know when something might go wrong.

For me, this is TOP missing feature now.

@RyanCavanaugh, there's been a bunch of feedback since the "Awaiting More Feedback" label was added. Can any typescript team members weigh in?

@nitzantomer I support the idea of adding throws to TypeScript, but wouldn't an optional try/catch allow for potentially inaccurate throws declarations when used in nested functions?

For a developer to trust that a method's throws clause is accurate, they would have to make the dangerous assumption that at no point in that method's call hierarchy was an optional exception ignored and thrown up the stack unreported. I think @ajxs may have alluded to it at the end of his comment, but I think this would be a large issue. Especially with how fragmented most npm libraries are.

@ConnorSinnott
I hope that I understand you right:

The compiler will infer thrown types, for example:

function fn() {
    if (something) {
        throw "something happened";
    }
}

Will actually be function fn(): throws string { ... }.
The compiler will also error when there's a mismatch between the declared errors and the actual ones:

function fn1() throws string | MyError {
    ...
}

function fn2() throws string {
    fn1(); // throws but not catched

    if (something) {
        throws "damn!";
    }
}

The compiler should complain that fn2 throws string | MyError and not string.

With all of this in mind, I don't see how this assumption is more dangerous than the assumption that other declared types that a developer trusts when using other libraries, frameworks, etc.
I'm not sure I really covered all the options, and I'll be glad if you can come up with an interesting scenario.

And even with this problem, it's pretty much the same as it is now anyway:

// can throw SyntaxError
declare function a_fancy_3rd_party_lib_function(): MyType;

function fn1() {
    if (something) {
        throws "damn!";
    }

    if (something else) {
        throws new Error("oops");
    }

    a_fancy_3rd_party_lib_function();
}

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") { ... }
        else if (e instanceof MyError) { ... }
    }
}

We can have typescript guard us even from this, but it will require a syntax which is a bit different from javascript:

function fn2() {
    try {
        fn1();
    } catch(typeof e === "string") {
        ...
    } catch(e instanceof MyError) {
        ...
    }
}

This will be compiled to:

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") {}
        else if (e instanceof MyError) {} 
        else {
            throw e;
        }
    }
}

Hey! Thanks for responding! Actually, having the throws inferred as you mentioned mitigates this issue I was thinking of. But the second example you listed is interesting.

Given

function first() : string throws MyVeryImportantError { // Compiler complains: missing number and string
    if(something) {
        throw MyVeryImportantError
    } else {
        return second().toString();
    }
}

function second() : number {
    if(something)
        throw 5;
    else   
        return third();
}

function third() : number throws string {
    if(!something)
        throw 'oh no!';
    return 9;
}

For me to explicitly declare MyVeryImportantError, I would have to also explicitly declare all the additional errors of the callstack, which could be a handful depending on the depth of the application. Also, potentially tedious. I certainly would not want to dive down the entire call chain to generate a list of potential errors which could arise on the way, but perhaps the IDE could help.

I was thinking of proposing some sort of spread operator to allow the developer to explicitly declare their error and just throw the remainder up.

function first() : string throws MyVeryImportantError | ... { // Spread the rest of the errors

But there would be an easier way to achieve the same result: just drop the throws declaration.

function first() : string { // Everything automatically inferred

Which raises the question: when would I see a benefit to using the throws keyword rather than just letting typescript infer the errors?

@ConnorSinnott

It's not different from other languages that use the throw clause, i.e. java.
But I think that in most cases you won't really have to deal with a lot of types. In the real world you'll usually deal with only string, Error (and subclasses) and different objects ({}).
And in most cases you'll just be able to use a parent class which captures more than one type:

type MyBaseError = {
    message: string;
};

type CodedError = MyBaseError & {
    code: number;
}

function fn1() throws MyBaseError {
    if (something) {
        throw { message: "something went wrong" };
    }
}

function fn2() throw MyBaseError {
    if (something) {
        throw { code: 2, message: "something went wrong" };
    }

    fn1();
}

As for when implicitly use the throw clause vs. having the compiler infer it, I think it's just like declaring types in typescript, in a lot of cases you don't have to, but you can do it to better document your code so that whoever reads it later can better understand it.

Another approach if you want type safety is to just treat errors as values a la Golang:
https://gist.github.com/brandonkal/06c4a9c630369979c6038fa363ec6c83
Still, this would be a nice feature to have.

@brandonkal
That approach was already discussed earlier in the thread.
It's possible, but doing so is to ignore an inherent part/feature that javascript allows us.

I also think that the noexcept specifier (#36075) is the simplest and best solution right now, as for most programers, throwing exceptions consider an anti-pattern.

Here are some cool features:

  1. Verifying functions will not throw an error:
noexcept function foo(): void {
  throw new Error('Some exception'); // <- compile error!
}
  1. Warning about redundant try..catch blocks:
try { // <- warning!
  foo();
} catch (e) {}
  1. Resolve always Promises:
interface ResolvedPromise<T> extends Promise<T> {
  // catch listener will never be called
  catch(onrejected?: noexcept (reason: never) => never): ResolvedPromise<T>; 

  // same apply for `.then` rejected listener,
  // resolve listener should be with `noexpect`
}

@moshest

as for most programers, throwing exceptions consider an anti-pattern.

I guess I'm not part of that "most programmers" group.
The noexcept is cool and all but it's not what this issue is about.

If the tools are there, I'll use them. throw, try/catch and reject are there, so I'll use them.

It'd just be nice to get them properly typed in typescript.

If the tools are there, I'll use them. throw, try/catch and reject are there, so I'll use them.

Sure. I will use them as well. My point is, that noexcept or throws never will be a good start to this issue.

Dumb example: I want to know that if I will call Math.sqrt(-2) it will never throw any error.

Same apply for third-party libraries. It gives more control on the code and the edge cases I need to handle as a programmer.

@moshest
But why?
This proposal includes all the benefits of the 'noexpect' proposal, so why settle for less?

Because it take ages (this issue is 3 years old), and I was hoping we at least can start with the most basic feature first.

@moshest
Well, I prefer a better feature that takes longer than a partial solution that will take less time but we'll stay stuck with it forever.

I feel like noexcept can be easily added in later, in fact it could be a separate topic.

noexcept is the same as throws never.

TBH I think it's more important to have some mechanism guaranteeing handling exceptions (ala not used error in Go) rather than providing type hints for try/catch

@roll there shouldn't be any "guaranteeing".
it's not a must to handle exceptions in javascript and it shouldn't be a must in typescript as well.

It could be an option as part of typescrypt's strict mode that a function must either catch the error or explicitly declare it throws and pass it along

To add my 5 cents: I see exceptions similar to a function returning null: It's a thing you normally don't expect to happen. But if it happens a run-time error occurs, which is bad. Now, TS added "non-nullable types" to remind you handling null. I see adding throws as taking these efforts one step further, by reminding you to handle exceptions as well. That's why I think that this feature is definitely needed.

If you look at Go, which returns errors instead of throwing them, you can see even clearer that both concepts aren't that different. Also, it helps you understand some APIs more deeply. Maybe you don't know that some functions can throw and you notice it much later in production (e.g. JSON.parse, who knows how many more there are?).

@obedm503 This is actually a philosophy of TS: by default the compiler doesn't complain about anything. You can enable options to treat certain things as an error or enable strict mode to enable all options at once. So, this should be a given.

I love this suggested feature.
Exception typing and inference can be one of the best things in the entire programming history.
❤️

Hello, I just spent a little of time trying to find a workaround with what we do currently have in TS. As there is no way to get the type of the thrown errors within a function scope (nor to get the type of the current method by the way) I figured out on how we could however explicitly set the expected errors within the method itself. We could later, retrieve those type and at least know what could be thrown within the method.

Here is my POC

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Set an extra type to any other type
type extraTyped<T, E> = T & extraType<E>

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<T, E extends Error> = extraTyped<T,E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;
const getTypedError = function<T extends basicFunction> (error, throwableMethod:T) {
    return error as getExtraType<ReturnType<T>>;
};

/***********************************************
 ** An example of usage
 **********************************************/

class CustomError extends Error {

}

// Here is my unreliable method which can crash throwing Error or CustomError.
// The returned type is simply our custom type with what we expect as the first argument and the
// possible thrown errors types as the second (in our case a type union of Error and CustomError)
function unreliableNumberGenerator(): throwable<number, Error | CustomError> {

    if (Math.random() > 0.5) {
        return 42;
    }

    if (Math.random() > 0.5) {
        new Error('No luck');
    }

    throw new CustomError('Really no luck')
}

// Usage
try {
    let myNumber = unreliableNumberGenerator();
    myNumber + 23;
}

// We cannot type error (see TS1196)
catch (error) {
    // Therefore we redeclare a typed value here and we must tell the method which could have crashed
    const typedError = getTypedError(error, unreliableNumberGenerator);

    // 2 possible usages:
    // Using if - else clauses
    if (typedError instanceof CustomError) {

    }

    if (typedError instanceof Error) {

    }

    // Or using a switch case on the constructor:
    // Note: it would have been really cool if TS did understood the typedError.constructor is narrowed by the types Error | CustomError
    switch (typedError.constructor) {
        case Error: ;
        case CustomError: ;
    }

}

// For now it is half a solution as the switch case is not narrowing anything. This would have been 
// possible if the typedError would have been a string union however it would not be reliable to rely
// on typedError.constructor.name (considering I would have find a way to convert the type union to a string union)

Thank you very much for your positive feedback! I realise it could be easier to refactor existing code without having to wrap the entire returned type into the throwable type. Instead we could just append that one to the returned type, the following code let you append the throwable errors as follow:

// now only append '& throwable<ErrorsThrown>' to the returned type
function unreliableNumberGenerator(): number & throwable<Error | CustomError> { /* code */ }

This is the only change for the example part, here is the new types declaration:

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<E extends Error> = extraType<E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;

type exceptionsOf<T extends basicFunction> = getExtraType<ReturnType<T>>;

const getTypedError = function<T extends basicFunction> (error: unknown, throwableMethod:T) {
    return error as exceptionsOf<T>;
};

I also added a new type exceptionsOf which allows to extract the errors of a function in order to escalate the responsibility. For instance:

function anotherUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : 100;
}

As exceptionsOf gets a union of errors, you can escalate as many critical method as you want:

function aSuperUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator> | exceptionsOf<typeof anotherUnreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : unreliableNumberGenerator();
}

I don't like the use of typeof, if I find a better way I will let you know

You can test here the result tips: hover typedError at line 106

@Xample That's a great solution with the tools that we have now!
But I think that in practice it's not enough since you can still do things like:

const a = superRiskyMethod();
const b = a + 1;

And the type of b is infered as number which is correct but just if it's wrapped inside a try
This should not be valid

@luisgurmendezMLabs I don't quite get what you mean. If you follow @Xample 's repo on line 56. You can see the result of myNumber + 23 is inferred as number, while myNumber: number & extraType<Error | CustomError>.

In another phase, a in your example is never wrapped in something like a Try Monad. It doesn't have wrapper at all other than an intersection with & extraType<Error | CustomError>.

Congratulations on the awesome design @Xample 👏👏👏👏👏👏. This is really promising, and already useful even without any syntactic sugar. Do you have any plan to build a type library for this?

@ivawzh My thoughts are that in practice this might bring some issues since:

function stillARiskyMethod() { 
    const a = superRiskyMethod();
    return a + 1
}

That function type return is infered as number and that's not entierly correct

@luisgurmendezMLabs the throwable type is within the return type of the function only as a way to stick the error's type with the function (and to be able to recover those types later). I never return errors for real, I only throw them. Therefore if you are within a try block or not won't change anything.

@ivawzh thank you for asking, I just did it for you

@luisgurmendezMLabs ah ok I understand your point, it seems typescript only infer the first type found. For instance if you had:

function stillARiskyMethod() { 
    return superRiskyMethod();
}

the return type of stillARiskyMethod would be inferred correctly, while

function stillARiskyMethod() { 
    return Math.random() < 0.5 superRiskyMethod() : anotherSuperRiskyMethod();
}

would only type the return type as superRiskyMethod() and dismiss the one of anotherSuperRiskyMethod(). I have not investigated more

For this reason, you need to manually escalate the error type.

function stillARiskyMethod(): number & throwable<exceptionOf<typeof superRiskyMethod>> { 
    const a = superRiskyMethod();
    return a + 1
}

I also just wanted to drop my thoughts in on this.

I think this would be a really good feature to implement as there are different use cases for wanting to return an Error/null and wanting to throw something and it could have a lot of potential. Throwing exceptions is part of the Javascript language anyway, so why shouldn't we give the option to type and infer these?

For example, if I'm doing lots of tasks that could error, I'd find it inconvenient to have to use an if statement to check the return type each time, when this could be simplified by using a try/catch where if any one of these tasks throws, it'll be handled in the catch without any additional code.

This is particularly useful when you're going to be handling the error's in the same way; for example in express/node.js, I might want to pass the error to the NextFunction (Error handler).

Rather than doing if (result instanceof Error) { next(result); } each time, I could just wrap all of the code for these tasks in a try/catch, and in my catch I know since an exception was thrown I'll always want to pass this to my error handler, so can catch(error) { next(error); }

Also haven't seen this discussed yet (May have missed it however, this thread has quite a few comments!) but if this was implemented, would it be made mandatory (i.e: Compilation error) to have a function that throws without using the throws clause in its function declaration? I feel like this would be nice to do (We're not forcing people to handle the throws, it would just inform them that the function does throw) but the big concern here is that if Typescript was updated in this way, it would likely break lots of currently existing code.

Edit: Another use-case I thought of could be this would also help with generation of JSDocs using the @throws tag

I will repeat here what I said in the now committed issue.


I think that typescript should be able to infer the error type of most of the JavaScript expressions. Allowing for faster implementation by library creators.

function a() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
}

function b() {
  a();
}

function c() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
  if (Math.random() > .5) {
    throw 'fairly lucky';
  }
}

function d() {
  return eval('You have no IDEA what I am capable of!');
}

function e() {
  try {
    return c;
  } catch(e) {
    throw 'too bad...';
  }
}

function f() {
  c();
}

function g() {
  a();
  c();
}
  • Function a we know that the error type is 'unlucky', and if we want to be very cautious we can extend it to Error | 'unlucky', hence includeError.
  • Function b will inherit the error type of function a.
  • Function c is almost identical; 'unlucky' | 'fairly lucky', or Error | 'unlucky' | 'fairly lucky'.
  • Function d will have to throw unknown, as eval is... unknown
  • Function e catches the error of d, yet since there is throw in the catch block, we infer its type 'too bad...', here, since the block only contains throw 'primitive value' we could say it cannot throw Error (Correct me if I missed some JS black magic...)
  • Function f inherits from c same as b did from a.
  • Function g inherits 'unlucky' from a and unknown from c thus 'unlucky' | unknown => unknown

Here are some compiler options I think should be included, as users engagement with this feature may vary depending on their skill as well as type-safety of libraries they depend on in given project:

{
  "compilerOptions": {
    "errorHandelig": {
      "enable": true,
      "forceCatching": false,   // Maybe better suited for TSLint/ESLint...
      "includeError": true, // If true, every function will by default throw `Error | (types of throw expressions)`
      "catchUnknownOnly": false, // If true, every function will by default throw `unknown`, for those very skeptics (If someone replaces some global with weird proxy, for example...)
      "errorAsesertion": true,  // If false, the user will not be able to change error type manually
    }
  }
}

As for syntax on how to express the error any function can produce, I am not sure, but I know we need the ability for it to be generic and inferable.

declare function getValue<T extends {}, K extends keyof T>(obj: T, key: K): T[K] throws void;
declare function readFile<throws E = 'not valid file'>(file: string, customError: E): string throws E;

My use-case, as showing an actual use-case might show other it has a value:

declare function query<T extends {}, throws E>(sql: string, error: E): T[] throws E;

app.get('path',(req, res) => {
  let user: User;
  try {
    user = query('SELECT * FROM ...', 'get user');
  } catch(e) {
    return res.status(401);
  }

  try {
    const [posts, followers] = Promise.all([
      query('SELECT * FROM ...', "user's posts"),
      query('SELECT * FROM ...', "user's follower"'),
    ]);

    res.send({ posts, followers });
  } catch(e) {
    switch (e) {
      case "user's posts":
        return res.status(500).send('Loading of user posts failed');

      case "user's posts":
        return res.status(500).send('Loading of user stalkers failed, thankfully...');

      default:
        return res.status(500).send('Very strange error!');
    }
  }
});

I need an error sink to prevent sending headers of response multiple times for multiple errors (they usually don't happen, but when they do they do so in bulk!)

This issue is still tagged Awaiting More Feedback, what could we do to provide more feedback ? This is the only feature I envy the java language

We have a use case where we throw a specific error when an API call returs non-200 code:

interface HttpError extends Error {
  response: Response
}

try {
  loadData()
} catch (error: Error | ResponseError) {
  if (error.response) {
    checkStatusCodeAndDoSomethingElse()
  } else {
    doGenericErrorHandling()
  }
}

Not being able to type the catch block ends up in developers forgetting that 2 possible types of errors can be thrown, and they need to handle both.

I prefer to always throw Error object :

function fn(num: number): void {
    if (num === 0) {
        throw new TypeError("Can't deal with 0")
    }
}
try {
 fn(0)
}
catch (err) {
  if (err instanceof TypeError) {
   if (err.message.includes('with 0')) { .....}
  }
}

Why does this feature still "not enough feedback"? It's so useful when invoking browser's API like indexedDB, localstorage. It's caused many failure in complex scenario but developer can't aware in programing.

Hegel seems to have this feature perfectly.
https://hegel.js.org/docs#benefits (scroll to "Typed Error" section)

I wish TypeScript has similar feature!

DL;DR;

  • The reject function of the promises should be typed
  • Any type thrown within a try block should be inferred using an union into the error argument of the catch
  • error.constructor should be properly typed using the real type and not only any to prevent missing a thrown error.

Okay, perhaps we should simply clarify the what are our needs and expectations:

Errors are usually handled in 3 ways in js

1. The node way

Using callbacks (which can actually be typed)

Example of usage:

import * as fs from 'fs';

fs.readFile('readme.txt', 'utf8',(error, data) => {
    if (error){
        console.error(error);
    }
    if (data){
        console.log(data)
    }
});

Where fs.d.ts gives us:

function readFile(path: PathLike | number, options: { encoding: string; flag?: string; } | string, callback: (err: NodeJS.ErrnoException | null, data: string) => void): void;

Therefore the error is typed like so

    interface ErrnoException extends Error {
        errno?: number;
        code?: string;
        path?: string;
        syscall?: string;
        stack?: string;
    }

2. The promise way

Where the promise either resolve or reject, while you can type the resolved value, you cannot type the rejection often called the reason.

Here is the signature of a promise's constructor: Note the reason is typed any

    new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;

It could theoretically have been possible to type them as follow:

    new <T, U=any>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: U) => void) => void): Promise<T>;

In this manner, we could still theoretically make a 1-1 conversion between a node callback and a promise, keeping all the typing along this process. For instance:

const fs = require('fs')
const util = require('util')

const readFilePromise = util.promisify(fs.readFile); // (path: PathLike | number, options: { encoding: string; flag?: string; } | string) => Promise<data: string, NodeJS.ErrnoException>;

3. The "try and catch" way

try {
    throw new Error();
}
catch (error: unknown) { //typing the error is possible since ts 4.0
    if (error instanceof Error) {
        error.message;
    }
}

Despite we are throwing an error "Error" 2 lines before the catch bloc, TS is unable to type the error (value) as Error (type).

Is it not the case neither using promises within an async function (there is no magic "yet"). Using our promisified node callback:

async function Example() {
    try {
        const data: string = await readFilePromise('readme.txt', 'utf-8');
        console.log(data)
    }
    catch (error) { // it can only be NodeJS.ErrnoException, we can type is ourself… but nothing prevents us to make a mistake
        console.error(error.message);
    }
}

There is no missing information for typescript to be able to predict what type of error could be thrown within a try scope.
We should however consider internal, native functions might raise errors which are not within the source code, however if every time a "throw" keyword is in the source, TS should gather the type and suggest it as a possible type for the error. Those types would of course be scoped by the try block.

This is only the first step and there will still be room for improvement, such as a strict mode forcing TS to work like in Java i.e. to force the user to use a risky method (a method which can throw something) within a try block. And if coder does not want to do so, then it would explicitly mark the function as function example throw ErrorType { ... } to escalate the responsibility of handling the errors.

Last but not least: prevent missing an error

Anything can be thrown, not only an Error or even an instance of an object. Meaning the following is valid

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a number or an object of type Error
    if (typeof error === "number") {
        alert("silly number")
    }

    if (error instanceof Error) {
        alert("error")
    }
}

To know that the error could be of type number | Error would be incredibly helpful. However to prevent forgetting to handle a possible type of error it is not really the best idea to use separate if / else blocs without a strict set of possible outcomes.
A switch case would however do this much better as we can be warned if we forgot to match a specific case (which one would fallback to the default clause). We cannot (unless we do something hackish) switch case an object instance type, and even if we could, we can throw anything (not only an object but also a boolean, a string and a number which have no "instance"). However, we can use the instance's constructor to find out which type it is. We can now rewrite the code above as follow:

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a Number or an object of type `Error`
    switch (error.constructor){
        case Number: alert("silly number"); break;
        case Error: alert("error"); break;
    }
}

Horray… the only remaining problem is that TS does not type the error.constructor and therefore there is no way to narrow the switch case (yet?), if it would do so, we would have a safe typed error language for js.

Please comment if you need more feedback

Was this page helpful?
0 / 5 - 0 ratings