Allow type annotation on catch clause variable #20024
Comments
|
I do like the idea of |
|
I agree, it is possible to safely annotate the error variable in many situations. Older discussion in #8677 (comment). It also true that it's easy to misunderstand/abuse, but then so are type annotations in general, and people just do the cast in the first line of the catch block anyway. |
|
@jaredru code like
is dangerous and misleading. It only passes the type checker because the parameter of the catch method's callback is declared to be Promise.reject(NaN).catch((e: Error) => e.message.length); |
|
I disagree. It is no more dangerous or misleading than The irony of your example is that it throws a |
I don't think that is a good pattern either. const foo = <Foo>JSON.parse(input);Is much clearer as to intent. As for the example, contrived as it may be, it doesn't matter what gets thrown ( |
|
Sorry, consider it a typo.
Of course it matters. That the runtime throws an |
|
My 2ct: If there is any party that should be able to type the exception it should be the called function, not the caller. I've seen codebases that throw strings everywhere or nothing at all ( if (err instanceof MyError) ... else throw err
switch (err && err.code) {
case 'ENOENT': ...
case 'EPERM': ...
default: throw err
}
const isExpectedHTTPError = (err: any): err is HTTPError => err && typeof err.status === 'number'
if (isExpectedHTTPError(err)) {
res.status(err.status).set(err.headers || {}).send(err.body || '')
} else {
res.status(500)
}And if the check doesn't pass, rethrow the error. If you don't and your usage of the error doesn't directly produce some kind of TypeError then you might swallow unexpected programming errors and create a debugging nightmare. Is this more code to write? Yes. But does that justify a new feature to make the unsafe version easier to write? |
|
My argument is for pragmatism. One of the stated non-goals of TypeScript's design is to "Apply a sound or 'provably correct' type system. Instead, strike a balance between correctness and productivity." Yes, I could get something besides an As in the TypeScript codebase itself (1 2 3 4 5), it is very often safe and reasonable to assume that the thrown value is not, in fact, |
|
TypeScript language is already the best I've ever worked. |
|
I'd like to at least be able to do
|
|
I would certainly love a |
|
need this |
|
It's not productive for me to guess which properties are on the Error and have to look it up. Please implement. |
I think it would be awesome ! |
|
I like @Andrew5569 's Idea, but want to have the ability to define it like I want: try {
// ...
} catch (err: unknown) {
// ...
}
try {
// ...
} catch (err: Error) {
// ...
}
try {
// ...
} catch (err: AWS.AWSError) {
// ...
} |
|
@Shinigami92 It will never be possible. TS is being transcoded to Javascript for execution. And Javascript does not implement the type. What would be possible is
|
|
@GabrielDelepine sorry you get me wrong. I only want to assume the type, not to define it. |
|
Yeah, please just fix this :-) The fact that this is not allowed is both inconsistent and inconvenient: try { ... } catch(error: Error) { ... }Especially considering that this is allowed: promise.catch((error: Error) => ... )You can't argue that allowing it would be dangerous, just because people might incorrectly assume the type is Not to mention, that in the vast majority of cases, assuming that the error will in fact be an While you are at it, please also add an optional reject type to promises: interface Promise<TResolve, TReject = any>Because yes, we can, and often do, guarantee that an async function will always reject with a certain type - and arguing against that is kinda ridiculous. We can just wrap everything in the function in a For example, we have an These would not be breaking changes, they would provide a lot of value, and people have been asking for it for years - so please just implement it :-) |
|
I would love it if it were implemented with a new Ability to define and infer expected error typesfunction foo(): Throws<Error> { // should be inferred
throw new Error()
}
try {
foo()
} catch (error) { // <-- Error
// noop
}Throw something other than an
|
|
Why do you think your approach does not accept this? function foo(): Throws<Error> { // should be inferred
throw new Error()
}
try {
const err: Throws<Error> = foo()
} catch (error) { // <-- Error
// noop
}I also think that |
|
@Shinigami92 good point on the breaking change. I don't think your example is any different than the function foo(): never {
throw new Error();
}
try {
const err: never = foo()
} catch (error) { // <-- any (existing implementation)
// noop
} |
|
+1 for having type-check flow through |
|
At the very least, you should be allowed to use a type that extends the Error type, as is done with
|
|
This is more-or-less addressed in #39015, which will land in 4.0. Not perfect, but better than nothing. |
|
@guidobouman I agree that #39015 is a small step towards this feature but in no way addresses it. That merge request is mostly does nothing to address #20024 IMO because it requires Something like this is what we're looking for: try {
doSomethingA();
doSomethingB();
doSomethingC();
} catch (e) {
if (typeof e === 'string') {
console.error(e);
} else if (typeof e === 'number') {
console.error('example error message:', e);
} else if (e instanceof Error) {
console.error(e.message);
} else {
throw e;
}
} |
|
Right now it's a bit verbose. What if I want to catch some exceptions and have it to continue to throw if it's not the one I wanted? try {
something();
} catch (err) {
if (err instanceof SomeError) {
doSomethingElse();
} else if (err instanceof AnotherError) {
doAnotherThing();
} else if (err instanceof YetAnotherError) {
yetAnotherThing();
} else {
throw err;
} // Else //
} // Try, Catch //What I wouldn't mind seeing is: try {
something();
} catch (err: SomeError) {
doSomethingElse();
} catch (err: AnotherError) {
doAnotherThing();
} catch (err: YetAnotherError) {
yetAnotherThing();
} // Try, Catch //Which would just output what was above. Would save me some typing and be pretty convenient like in Java and other languages. |
|
Multiple catches are not a pattern that is known to JavaScript. Feels weird to change the structure of the language. |
Neither is private/public constructor fields, or public/protected/private class fields, etc etc.. Need I go on? |
Microsoft did those features in the earlier days of TypeScript, but not long after decided to more closely align with the ECMAScript specification, which they have done extremely well since. Their design goals are clear and they will not be introducing features like this that deviate from the specification. |
|
The whole point of Typescript is to type check as a transpiler to JavaScript, which is why we're not using vanilla JavaScript. If you feel a feature could benefit many others, but dismiss it because it "feels weird" but makes sense in other languages and is successful and many use those features, why do you raise your voice against it? We use Typescript for the sugar that it provides. Decorators? Typed variables? Type checks? Generics? Function overloads? Don't forget the new variadic tuple types in typescript 4. The point is, it's meant for a more Java or OOP programming that helps you have more typed and safe control over your code. It will always run JavaScript, that's a given (as I think they stated that as a mission goal). But we shouldn't feel weird when using a language that transpiles into another language if there's differences.. there will always be differences, and that's good.. Typescript !== JavaScript. |
Can you provide a source reference to this decision? I can't seem to find it. I'd like to be a part of that discussion. That's why I'm asking where you found it. |
|
I don’t use TypeScript for the sugar it provides. The sugar is the very thing that is not in their design goals today. It’s more about confidence and type safety and all the type information disappears at compile time, as it should. Decorators are another one of the earlier features they implemented before ironing out the design goals, so it’s irrelevant to your argument. |
|
I don’t think you’re going to get any traction requesting to change the design goals, BTW. |
See Goals here: https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals |
How would the multiple catch clause go against the mission goals exactly? Also, they're aligning "with current and future ECMAScript proposals", not with the specification itself. There's a difference. And I'm not requesting to change the design goals. And again, I was asking for the reference to the decision to do this, as was mentioned. The commenter said there was a decision and I only would like to know where it is. :) |
Are you speaking on behalf of Microsoft? Are you on the Microsoft team? Who's specification? JavaScript? Well introduce types and immediately it's against JavaScript specification.. what's your point? In fact, I think this "aligns" with goal #1 very nicely.. "1. Statically identify constructs that are likely to be errors." By clarifying in more detail to exactly catch the different typed errors visually and cleaner, than using many if statements in one single catch block, and safely rethrow if none are found.. just like others are most likely used to coming from java and the like. |
|
I’ve been following the project since the beginning and I’ve seen and even started enough of these types of conversations to know where Microsoft stands on these points. I didn’t like it at first, but now I 100% get it and respect their design goals. There are other ways to statically identify constructs that are likely to be errors without adding new JS syntax that requires not just throwing away type information at compile time, but also modifying the AST. I think the best way to think about it is if you throw away all the type information, the JS that is left should ideally be runnable, which might also require shims, but runnable without modification to the AST. |
|
Again, you seem to think this is against the design goals? Many changes in the language including types, change the AST (Abstract Syntax Tree). Have you ever worked on a compiler? I have (if anyone is curious I did a bit of work on the Free Pascal compiler). I referenced what the JavaScript could would be here: #20024 (comment) What the transpiler would emit. It's safe, simple, straight forward. Requires no shims, and does not somehow modify JavaScripts AST. :) |
|
The first non-goal states the following:
If this were Python, your request here would make perfect sense, as Python already supports it like so: try:
print(x)
except NameError:
print("Variable x is not defined")
except:
print("Something else went wrong")But this is not Python. Non-goal #5 states the following:
You can see the problem pretty quickly if you try to transpile your proposal into JS. My best attempt would be the following: try {
something();
} catch (err) {
if (err istanceof SomeError) {
doSomethingElse();
} else if (err instanceof AnotherError) {
doAnotherThing();
} else if (err instanceof YetAnotherError) {
yetAnotherThing();
}
}Notice how this now "requires runtime metadata"; furthermore, what should happen if |
It's against the non-goals, as stated above. Also, you're looking for "sugar," which is not in the design goals.
Sure, but my point was "after the types have been removed."
I translated the entire PostCSS project into TypeScript, but I don't see how that's relevant. I've also used the TypeScript compiler API to manipulate and create AST, so I'm very aware of how it works.
It's only simple in your exact use case and only if all the types are classes or functions (instanceof-able). It also modifies the AST, for sure. I'm not sure why you think that it doesn't, as it has to translate catch clauses into separate |
It requires no runtime metadata, as you can see. :) I asked if you worked on a compiler because you mentioned it modified the AST, which most real typescript language changes do? Arrow functions in fact translate to "function ()" and surrounds by an enclosure if I'm not mistaken? Either way, features like that drastically change the emitted AST.. Like most of the features when they target ES5. Anyway, I'm done debating on this. If others would like it, they can voice. |
It absolutely does. The
Only to support older targets, but you are mistaken if you are talking about compiling to a modern target. Aside from access modifiers and decorators, you should be able to compile |
Hey all, let's please be respectful in tone - this can come off as intimidating. @cyraid Even if this is a syntactically-driven construct (i.e. only entities that resolve to both a value and a type are allowed in those
On TypeScript, we intentionally aim not to add new constructs that have any sort of runtime behavior at all if they are not part of ECMAScript, and that stance has hardened over the years. |
|
Sorry, my answer was a bit short. This discussion has exploded a bit, but let me explain my initial reasoning: Sure, they (most typescript features) are additions, but not structurally. Most annotations can be removed without transpilation. I'm a fan of terser forms of expressing a goal. But not when it changes the mental flow of the language, and might possiby collide with the development of EcmaScript itself. That's also reflected in their design goals, particularly:
See #18500 (comment) as a reference. |
|
I wonder when will this issues be implemented? |
|
For now, you can do this with type casting: /**
* Add a new waitlist entry.
*
* @return {Promise}
*/
async addWaitlist(): Promise<void> {
try {
const response = await this.$waitlistRepository.AddWaitlist(
'test41@test.com'
)
console.log(response.message)
} catch (e) {
const error = <ErrorAPIResponseInterface>e
console.log('RESULT', error.message)
}
}The error constant has intellisense-enabled for your API error custom response. |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.

TypeScript Version: 2.6.1
This was discussed in #8677 and #10000. It was closed as "fixed" in #9999, but as far as I can tell neither of the issues was actually resolved. In either event, I'd like to make a case for allowing type annotations in catch clauses.
Especially with the introduction of downlevel async functions, I'd suggest that disallowing catch clause type annotations leads to less safe code. In the example, the two methods of handling the promise are functionally equivalent, but one allows you to type the error, and the other doesn't. Without writing extra code for the
try/catchversion (if (err instanceof Error) {orconst e: Error = error something), you'll get a runtime error that you wouldn't get with the purePromiseversion.The primary rationale for not allowing this is that any object can be thrown, so it's not guaranteed to be correct. However, most of the benefit of TypeScript comes from making assertions about your and other people's code that can't be strictly guaranteed (especially when importing JavaScript). And unless one would argue that the
Promisecatch function also shouldn't allow a type annotation on the error parameter, this argument seems to make very little practical sense.I believe one of the other arguments against is that it might be confusing, as it looks like the typed exception handling you might see in other languages (e.g., Java), and folks may think the catch will only catch errors of the annotated type. I don't personally believe that's a legitimate issue, but if it really is I'd propose at least allowing a
catch (err as Error) {syntax or similar as a way of emphasizing that it's a type assertion.If nothing else at all, it seems that there should be a way to trigger a warning (similar to an implicit any warning) when using an untyped
errdirectly within a catch block.The text was updated successfully, but these errors were encountered: