Repository navigation
Allow type annotation on catch clause variable #20024
Description
TypeScript Version: 2.6.1
const rejected = Promise.reject(new Error());
async function tryCatch() {
try {
await rejected;
} catch (err: Error) { // TS1196: Catch clause variable cannot have a type annotation
// Typo, but `err` is `any`, so it results in runtime error
console.log(err.mesage.length);
}
}
function promiseCatch() {
rejected.catch((err: Error) => { // OK
// Compiler error; Yay!
console.log(err.mesage.length);
});
}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/catch version (if (err instanceof Error) { or const e: Error = err or something), you'll get a runtime error that you wouldn't get with the pure Promise version.
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 Promise catch 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 err directly within a catch block.
Activity
DanielRosenwasser commentedon Nov 15, 2017
I do like the idea of
catch (err as Error)because, damn it, if you were going to cast it anyway, maybe we should make your life a little easier while keeping it explicit.yortus commentedon Nov 15, 2017
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.
aluanhaddad commentedon Nov 20, 2017
Jared Russell (@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
any. At best this is a disguised type assertion.jaredru commentedon Nov 21, 2017
I disagree. It is no more dangerous or misleading than
const foo: Foo = JSON.parse(input);, which is a very reasonable pattern. It represents our expectations and assertions about the code we're dealing with.The irony of your example is that it throws a
TypeError, which gets at my underlying point. Aside from contrived examples, the expected currency for errors isError. A simple search through the TypeScript repo itself shows thatcatch (e)virtually always assumes theeto be anError. That doesn't make it "right", of course, but it highlights the realistic expectation of a large, real-world project that (presumably) reflects an understanding of the balance between correctness and pragmatism, particularly as it relates to TypeScript.aluanhaddad commentedon Nov 21, 2017
I don't think that is a good pattern either.
Is much clearer as to intent.
As for the example, contrived as it may be, it doesn't matter what gets thrown (
TypeErroror not) because it is thrown from thecatchcausing an unexpected failure.jaredru commentedon Nov 21, 2017
Sorry, consider it a typo.
<Foo>JSON.parse(s)is a common pattern but is as unsafe as.catch((e: Error) => {. Both treat ananyas another type with no guarantee of correctness beyond the developer's word.Of course it matters. That the runtime throws an
Errorfor all runtime errors strengthens the position that it's reasonable to expect the argument tocatchto be anError. This expectation is common in real world codebases--including, but certainly not limited to, TypeScript's.felixfbecker commentedon Nov 28, 2017
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 (
undefined). Just declaring that the type isErrordoesn't make it more type safe, because you never know what other functions the function you are calling may call into and what things those nested functions throw - Errors are not strongly typed and not part of the interface contract. Maybe you know it now but in the next patch version of some transient dependency it could change. So the only correct way to mirror the runtime JS is to start withanyunknownand narrow it down manuallyAnd 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?
jaredru commentedon Nov 28, 2017
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
Error, but in reality--in the projects I'm working in--I won't.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,
undefined(ornull, or a string, ...) and is with almost certainty anError. I would simply like the compiler to help me out in this case without additional code or runtime overhead.lordazzi commentedon Jun 1, 2018
TypeScript language is already the best I've ever worked.
If you type the catch clause this will make much better.
felixfbecker commentedon Aug 31, 2018
I would certainly love a
exceptionTypeUnknownflag that makes all exception types (and Promise rejections) typeunknowninstead ofany. That would definitely make the error handling safer as opposed to allowing any annotation. Although this could also be solved by writing a TSLint rule that forces them to always be declaredunknown, if we had this annotation.63 remaining items
vegerot commentedon Sep 23, 2021
Anton Bessonov (@Bessonov) I agreed with everything you said up to this point. By the points you've given, then
anyshouldn't be allowed either!anyis even worse thanMyExceptionType!My real issue with it is its inconsistency.
If I were the TypeScript dictator, I would allow no type annotations in
catchclauses. However, sinceanyis worse than any other annotation, if you allowanyI think it makes sense to allow other types tootonycoco commentedon Sep 29, 2021
You could just cast to
Errorinside thecatch:In TypeScript 4.4+, those errors are now
unknownin thecatch. Or, you just type check like they do in the example here: https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#use-unknown-catch-variablesmckravchyk commentedon Oct 25, 2021
This could be really useful for test suites after 4.4 defaults to unknown. In test suites, defaulting to unknown is not appreciated as generally you know which error you expect and even if for some reason the code threw anything else, it would pop up on the test anyway.
Dador commentedon Nov 3, 2021
Shinigami (@Shinigami92)
It's possible. You can check error type inside
catchand re-throw error if there was no intend to catch it. Just as suggested in other comments from @zzzzBov and from @cyraid.So with this solution
catchwill work just as in most typed languages.Actually it looks like a good solution. Or is there something wrong with it?
octogonz commentedon Feb 11, 2022
Bodo Graumann (@bodograumann) As of TypeScript 4.4 you can set
useUnknownInCatchVariables=falsewhich restores the oldanybehavior. It's not ideal, but nobody was complaining about this in TypeScript 3.x. (In fact the TypeScript compiler itself is currently building this way -- if you setuseUnknownInCatchVariables=trueinsrc/tsconfig-base.json, the build fails.)spephton commentedon Apr 29, 2023
A nice way to safely unwrap your
unknownerror is to useinstanceof(as mentioned upthread)You're explicitly handling all possibilities, so it's type-safe.
I think catch variable as unknown is a good default as it encourages you to think about the values that variable can take.
It may make sense to have a tsconfig flag to allow
catch (e: Error)to ease the pain for some users however.dimikot commentedon Jan 30, 2024
Another (type safe) option is to allow to safely cast to something like:
where MaybeError is some custom type defined similar to:
I.e. allow
e: MaybeErrorif MaybeError is null, undefined, or is an object with all props being optional and of typesunknown. In this case, it still allows to do lots of things (checking for props being present, checking for equality likee?.code === "some"etc.), but at the same time, does not compromise type safety.This would work, because, when MaybeError is defined the above way, there is no JS thing which would not match it. Even null or a plain string or a number thrown will still match.
ozanmuyes commentedon Jul 7, 2025
Why would anyone want to annotate something in a catch clause to 'any' or 'unknown'?! Just why? What's the gain? I mean if it wasn't annotated it's 'unknown' by default. Why would I want to get the same type with more keystrokes? What was the logic behind?
Ideally I want to annotate it as a class that extends Error, not even Error. What else could be in a catch clause, except for an Error? 🤦🏻♂️
Look all those issues (that I came across) about the same, nonsensical restriction; 8677, 9999, 20024 (this one), 42596
Bessonov commentedon Jul 7, 2025
Ozan Müyesseroğlu (@ozanmuyes) You're probably new to software development. The short answer is that this would be an incorrect assumption. Try this one:
ozanmuyes commentedon Jul 7, 2025
Anton Bessonov (@Bessonov) If that is programming then yes I'm new to it. From now on I'll throw numbers and let sysadmins guess what I meant. So practical!
RyanCavanaugh commentedon Jul 7, 2025
Literal TC39 committee members unironically throw non-Error objects in JavaScript. It's a real thing.
https://x.com/ljharb/status/1611765541085278210
Bessonov commentedon Jul 7, 2025
Ozan Müyesseroğlu (@ozanmuyes) I'm not sure sysadmins care what you throw - unless you're throwing it at them 🤷
From a practical perspective, I'd love it if the TypeScript team would reconsider their goal of not altering runtime behavior and allow something like:
which could be translated in something like:
EDIT: Nothing new, proposed already here: #20024 (comment)
ozanmuyes commentedon Jul 7, 2025
I don't think I was clear — let me retry.
The ability to
throwanything to break execution flow in atryblock is a JavaScript thing — and I’m neutral about it. Still, I believe the majority of JS/TS developers use thecatchblock to handle Errors (and the like), which aligns with other programming languages. Can this be stretched? Yes — and if so, so be it. I personally don't — it's just a matter of preference.All I really want is to get rid of this hack
(e as Error)during development — and be able to typee.and have Intellisense suggest mename,message,cause?, andstack?;Instead being able to annotate on the
catchclause;In the working snippet above, explicit casting with
asis just as error-prone as plain JavaScript, and not truly type-safe. It still requires type guards to ensure runtime safety.tsctrusted me, and I let it down — that’s on me. But then, why can't I do the same thing by simply annotating? Ifeisunknownby default, then what’s the practical benefit of restricting annotation to onlyunknownorany?TL;DR
Also I believe either I shouldn't be allowed to annotate
eat all — or, if I can, I should be free to use any type I want (union or not).