You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I acknowledge that issues using this template may be closed without further explanation at the maintainer's discretion.
I love the irony that a maintainer was forced to check this box 😄
Reacted by Obed, Michael Angelo Rivera, Kyle Venn, Angelo Rivera, Cézar Augusto Nascimento e Silva, Josh Ghoulberg 👻, Reece Como, Sahin Deniz, Lazar Ljubenović, eklenin and 8 more
Kyle Kashuba (@KashubaK)Kyle Venn (@kvenn) I'm going to start looking at intellij (and also see if I can make an eslint plugin for it!). If you want to help, let me know!
Reacted by Kyle Venn, Marek Lukáš, Angelo Rivera, Tim Etler, Aylon Carrijo and Zefir
Would be happy to be involved. I did like the idea of ESLint plugin because it's piggybacking off of an already established static analysis solution, but I think IDE plugins also check that box.
This comment has some code for the starting point of the ESLint plugin (in a collapsed text block): #13219 (comment)
Would be happy to be involved. I did like the idea of ESLint plugin because it's piggybacking off of an already established static analysis solution, but I think IDE plugins also check that box.
This comment has some code for the starting point of the ESLint plugin (in a collapsed text block): #13219 (comment)
I've since gotten the opportunity to try out the JetBrains plugin for does-it-throw and after looking into it a bit more, I don't think that really solves the problem I'm having with exceptions.
That plugin seems to mostly be about alerting of where throw statements are used. Which appears to be for enforcing that you don't use throw statements. I think throw statements are here to stay, even if I agree first class support for errors has advantages. And that if you're already in a codebase which relies on throws, this adds a lot of noise.
I had proposed an ESLint exception to warn when invoking a function that can throw. Encouraging you to either mark(document) this function as one that re-throws or to catch it. With the intention being to prevent you from accidentally having a function that throws bubble up all the way to the top of your program. But allowing that to be the case if it makes sense (like in a GraphQL resolver, where the only way to notify Apollo of the error is via throwing, or a cloud function / queue where throwing is used to retry).
If it can be found implicitly (without documentation), that's better. And it seems like a plugin could actually achieve that (and even offer quick fixes, which would be SO COOL). I'd advocate for using an already used standard TSDoc annotation (@throws) as the acknowledgement that this throw statement is there on purpose (as opposed to introducing a new one - @it-throws or @does-it-throw-ignore).
does-it-throw has some great bones. And it seems like it's solving a problem for others, it just might not be the right fit for me.
Reacted by Nick Chevsky, Kyle Kashuba, zroug, Huynh Duc Duy and Luca Visentin
Checks to see if a function you're calling has a @throws JSDoc annotation. If you don't wrap the function call in a try/catch it will output an error.
undocumented-errors
Warns you if a function has a throw statement without a corresponding @throws annotation. Matches based on what you're throwing, in case you have custom error classes.
Check out the tests for examples and what it covers. It's been a while since I looked at this, but I remember it being a bit buggy (i.e. nested branches, complicated logic) so there's a ton of room for improvement. I might take another look to improve it.
Side note - the README suggests you can install it from NPM, this is not the case haha.
(This is my work GH account, dunno why I have a separate one but oh well. I previously contributed here as Kyle Kashuba (@KashubaK))
Reacted by Kyle Venn, Vladyslav Zubko, dQw4w9WgXcQ, Huynh Duc Duy, Grégoire Geis and loki
Reacted by Kyle Venn, Ian Luca, dQw4w9WgXcQ, Sebastian Garrido, Ray Thurn Void and loki
I'm definitely a complete noob when it comes to how Javascript/Typescript works, but would it be possible to add the modifier throws to a Typescript function signature?
As both a Typescript user and a library maintainer, I hate not being able to consume / deliver proper error declarations. I know I can use JSDoc's @throws, but having that keyword as part of the function signature would be so great...
The TL;DR is essentially, it's not worth doing because there isn't sufficient existing practice/documentation/runtime behavior to facilitate such a feature. TypeScript is designed to fit within the scope of JS' behavior, and since JavaScript doesn't give us reliable, native tools for things like checked exceptions it's challenging to fit it within scope.
What we're pondering now is, what's the next best thing? How can we encourage better error handling practices enough that the community has some common ground to operate on?
I was thinking about the newly introduced type guards / type predicates, in my mind it seemed totally possible, especially knowing we have conditional types as well.
I'll keep an eye on the eslint solution, that makes sense to me knowing what you just explained. thanks!
I was thinking about actual "throws" keyword as optional in return type, so TS would automatically add defaults to current functions/methods .. something like throws<any> or throws<unknown> at least for starting point.
so when we do write modules we can actually more strictly expose what type of things we are throwing out like example.
and maybe have some utility type similar as typeof to extract errors types from function so we can more easily utilize other modules throw types without directly importing those.
Also maybe this have later impact on catch argument type to actually know throw types, but just actual documentation of throw types is way more important atm for interoperability between modules as currently we are just quessing and reading module source code to undestand what might actually get thrown.
Edit:
this would also work for indication that function will never throw .. throws<never>
Also maybe this have later impact on catch argument type to actually know throw types
This doesn't really work unless you have a level of information that doesn't exist in the real world, and requires the ability to express many patterns that are basically arbitrarily complicated. See #13219 (comment)
but just actual documentation of throw types is way more important atm for interoperability between modules
If documentation is the issue, you don't need a TS keyword -- https://jsdoc.app/tags-throws has existed for ages. I don't know about you, but I really don't see it used very often. This is the heart of the problem Ryan described in the comment linked above (summarizing the original issue): JS developers don't, broadly speaking, document expected exception behavior, so there's a chicken and egg problem where trying to implement checked-exception types would go against the grain of the current ecosystem.
Use of the @throws JSDoc tag can be treated as a sort of demand signal for better exception handling. Poor @throws adoption indicates that the community doesn't want it. And without good @throws coverage, a throws keyword in TS wouldn't be very useful in the best scenario, and would be actively misleading at worst, giving devs the impression that they've handled the "expected" throw scenarios when they haven't.
All that said, I still think there could be a place for some limited ability to perform static analysis of exception/rejection handling. I originally found the previous issue when I enabled a linter rule that looks for unhandled Promise rejections, which overlaps with try/catch once await enters the picture. I was looking for a way to decorate some async function calls as being unable to throw or reject (think return Promise.resolve('static value')), which would let me build out exception-safety from the bottom up, slowly. Maybe this could work if we split the feature into declaring or asserting throws-type (basically the @throws JSDoc tag), with a separate keyword or directive for enabling exception checking:
/** unsafe-assertion-that-this-throws-strings */functionthrowsSometimes(): number{if(Math.random()<0.5){throw'nope!';}return(Math.random()<0.5) ? 0 : 1;}/** unsafe-assertion-that-this-throws-never */functionthrowsNever(): number{returnJSON.parse('2');}/** checked-assertion-that-this-throws-never */functionmaybeSafe(): number{returnthrowsSometimes()||throwsNever();// error, unhandled throws-strings does not match declared throws-never}
Note that this is a different scope from what was discussed in the checked-exceptions section of #13219 (comment). I'm trying to statically analyze that explicit/declaredthrows propagate correctly up the chain, and importantly, to limit where those checks are performed. I want to be able to decorate one function as not calling functions with decorated/expected exceptions outside of a try block -- to annotate one function as "exception-prone" and another as "bad at exception handling". I think there's value in that even if most library functions I call don't (currently) have their expected exceptions documented. (In Ryan's terminology, this requires "option one", unannotated functions are assumed not to throw anything.)
And if you don't plan to talk about exceptions, it might make sense to move your responses to a different thread.
We are talking here about Typed Exceptions.
The position of TS team is that unknown is an exact type for exceptions.
But really there are cases when we need a typed errors and we need to utilize type system to check that everything is handled correctly.
In TS -- exceptions are not a good way to solve this, considering current ecosystem state. The Result is not a unique way to solve that.
The solution is simple -- don't throw in non exception cases, and that it. And it is also talking about the exception.
Now it looks like such frameworks like tRPC use exceptions in goto style -- and then we have arguments here that we need to type errors.
Regarding this:
... TSDoc is the only way to declare it, which has limitations and isn't exactly bullet-proof, since a single typo can break it. ...
yes, it is not a bullet-proof, throws in Java is also not a bullet-proof, because in any moment it could be something like: throws Throwable -- the same as throws unknown.
no, the typo doesn't break anything. Like any typo in the documentation as well. Yes, it will complicate development, but it will not break the program.
You really can't explore a problem space without examining the tangential issues. Discussing other solutions and where and when exceptions don't need to be "solved" adds at least as much value as rehashing the same points again for the nth time.
yes, you are right, I didn't check the correct type for this cases. It must be just DOMException:
interfaceHTMLVideoElement{/** * Loads and starts playback of a media resource. * * @throws `DOMException & { name: "NotAllowedError" }` if the user agent (browser) or operating system doesn't allow playback of media in the current context or situation * @throws `DOMException & { name: "NotSupportedError" }` if the media source (which may be specified as a MediaStream, MediaSource, Blob, or File, for example) doesn't represent a supported media format * * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) */play(): Promise<void>}
JSDoc doesn't have the @rejects, but Monaco Editor understands it as well. But it doesn't change the approach, as I said before: any "typo" in documentation doesn't break the program.
~project\node_modules\typescript\lib\_tsc.js:123141
throw e;
^
Error: Debug Failure. False expression: jsx should have errors when reporting errors
at getSignatureApplicabilityError (~project\node_modules\typescript\lib\_tsc.js:76047:15)
at resolveCall (~project\node_modules\typescript\lib\_tsc.js:76495:25)
at resolveJsxOpeningLikeElement (~project\node_modules\typescript\lib\_tsc.js:77292:12)
at resolveSignature (~project\node_modules\typescript\lib\_tsc.js:77334:16)
at getResolvedSignature (~project\node_modules\typescript\lib\_tsc.js:77351:20)
at checkJsxOpeningLikeElementOrOpeningFragment (~project\node_modules\typescript\lib\_tsc.js:74639:17)
at checkJsxSelfClosingElementDeferred (~project\node_modules\typescript\lib\_tsc.js:74140:5)
at checkDeferredNode (~project\node_modules\typescript\lib\_tsc.js:86652:9)
at Set.forEach (<anonymous>)
at checkDeferredNodes (~project\node_modules\typescript\lib\_tsc.js:86617:27)
The error happens when adding a check before a type:
export type ObjectKeysOfType<
T extends object = object,
P extends unknown = unknown,
- > = InnerObjectKeyOfType<T, P>;+ > = keyof T extends never ? string : InnerObjectKeyOfType<T, P>;
In the editor are no errors
Info:
node: v22.17.0
yarn: 1.22.22
typescript: tried both 5.8.3 & 5.9.2
As context, error happens in a workspace library which is part of a monorepos with 30 Frontends (NextJS)
I don't know if we have to much type inference or stuff happening but I didn't saw this error message anywhere
I am interested in your thoughts.
Kind Regards Christopher
Reacted by Jendrik, Brett Wood, Thomas Berrios, Milanin and Kevin Ramharak
Instead of trying to model all possible runtime errors, it only type-checks the exceptions that are part of the program’s declared model - similar to how TypeScript treats types vs any.
The prototype:
tracks thrown and rejected types
enforces handling at call sites (try/catch, await, .catch, etc.)
integrates with TS-style inference and assignability
supports incremental adoption via a compiler option + new // @ts-expect-exception suppression
can be used in library declaration files via throws/ rejects
I made a lot of intentional design decisions to make this feel as "TypeScript-native" as possible, but I think there are still some obvious tensions with encouraging exceptions as control flow, which I discussed here.
One thing I notice that isn't covered in your docs (and that seems to be at the core of Dmytro Shchehlov (@DScheglov)'s example) is function assignability in the presence of a throws clause. So a simpler version of the example would be:
declarefunctionthrowsError(): voidthrowsError;declarefunctionthrowsSyntaxError(): voidthrowsSyntaxError;letx=throwsSyntaxError;x=throwsError;// should this be allowed
The current playground does allow it, and I think that may be the right choice, but it means that the error Dmytro Shchehlov (@DScheglov) is hoping to get is intentionally out of scope.
Also, on the off chance you missed it (because this thread is so freakin' long), I made a similar proposal a while back that might be of interest: #57943
I think there are two related but separate questions here:
What should happen when a let-declared function variable is reassigned to a
function with a wider throws type?
What should happen when a callback is contextually typed by a parameter whose
contract says throws never or rejects never?
For the first case, let's improve your example with a nominal-like custom error type (Error is assignable to SyntaxError):
classCustomerSyntaxErrorextendsError{name="CustomerSyntaxError"asconst;}declarefunctionthrowsError(): voidthrowsError;declarefunctionthrowsSyntaxError(): voidthrowsCustomerSyntaxError;conste: CustomerSyntaxError=newError();// (2322) Type 'Error' is not assignable to type 'CustomerSyntaxError'.letx=throwsSyntaxError;x=throwsError;
Considering that regular TypeScript allows this:
letx: 10|20=10;// x: 10 | 20x+=1;// x: number
it makes sense to allow thrown exception types to widen for variables declared with let.
However, in this case, after the assignment, the type of x must be inferred as function (): void throws Error instead of the current function (): void throws CustomerSyntaxError.
The controller(cb: () => void throws never) case seems different. It is more similar to this:
declarefunctionfn(y: 10|20): void;lety: 10|20=20;fn(y+1);// ~~~~~// (2345) Argument of type 'number' is not assignable to parameter of type '10 | 20'.
The controller case is important and should not be intentionally excluded from the scope, because declaring an error boundary is one of the motivations for typed exceptions, because, it allows to define a place where developers are forced to handle errors and ensure their code does not throw or reject.
For example, an Express-like application could declare its get method so that route handlers must not throw or reject:
interfaceApplication{get(path: string,handler: {(req: Request,res: Response,next: NextFunction): voidthrowsnever;(req: Request,res: Response,next: NextFunction): Promise<void>throwsneverrejectsnever;},): void;}declarefunctionparseUuid(input: string): input is UUIDStringthrowsUUIDParsingError;declarefunctionfindUser(id: string): Promise<User>rejectsUserNotFoundError;app.get("/users/:id",async(req,res)=>{constuserId=parseUuid(req.params.id);// ~~~~~~~~~// The handler violates the `rejects never` contractconstuser=awaitfindUser(userId);// ~~~~~~~~// The handler violates the `rejects never` contract.//// Actually, ErrorScript reports an error here even if `.get` allows// rejection with Error, which is also incorrect.res.json(user);});
The controller case is important and should not be intentionally excluded from the scope, because declaring an error boundary is one of the motivations for typed exceptions, because, it allows to define a place where developers are forced to handle errors and ensure their code does not throw or reject.
I think you'd say that the inferred throws type of callSafely is never. In that case, this should be legal:
controller(()=>{callSafely(throwsSyntaxError);});
However, presumably you would not want this to be legal, because the thrown error will actually not get caught at runtime:
controller(()=>{callSafely(throwsError);});
But now — if we stick with the idea from earlier that x = throwsError; is allowed — then we're saying that one can assign throwsError to a let variable declared/initialized as typeof throwsSyntaxError, but cannot assign throwsError to a function argument with that same type!
I don't think it's workable to have different rules for assignability to a variable as to a function arg, etc. If you wanna say that throws effects assignability everywhere, then ok, using throws never can work because callSafely(throwsError); just wouldn't be allowed. (Although the TS team has pushed back on error types influencing assignability for other reasons.) But I don't think you can say that assignability depends on whether it's to a callback or a variable.
Thanks for the support on this - sorry I've taken a while to get back (I've had to think about it)!
I think I understand the concern, and to confirm, in the latest version of ErrorScript, effects are included in function assignability.
TS only allows "widening" if the two types are assignable, so this is OK and expected:
letnum: 10|20=10;// x: 10 | 20num+=1;// x: number ✅
However, this gives an error (and is also expected):
classCustomSyntaxErrorextendsError{name="CustomSyntaxError"asconst;}conste1: Error=newCustomSyntaxError();// OK ✅conste2: CustomSyntaxError=newError();// ERROR: (2322) Type 'Error' is not assignable to type 'CustomSyntaxError'. ✅
Since CustomSyntaxError is assignable to Error but not vice versa.
The same applies to effects: () => void throws CustomSyntaxError is assignable to () => void throws Error but not vice versa.
This means we can have code like this:
classCustomSyntaxErrorextendsError{name="CustomSyntaxError"asconst;}declarefunctioncontroller(callback: ()=>voidthrowsnever): void;declarefunctionthrowsError(): voidthrowsError;declarefunctionthrowsSyntaxError(): voidthrowsCustomSyntaxError;functioncallSafely(cb: ()=>voidthrowsError): void{try{cb();}catch(e){if(einstanceofError){// ✅ changed impl here such that the inferred type of `callSafely` is `throws never`.return;}throwe;}}functioncallSafelySyntax(cb: ()=>voidthrowsCustomSyntaxError): void{try{cb();}catch(e){if(einstanceofCustomSyntaxError){return;}throwe;}}controller(()=>{callSafely(throwsSyntaxError);});// OK as callSafely acecpts any Error ✅controller(()=>{callSafely(throwsError);});// OK ✅controller(()=>{callSafelySyntax(throwsSyntaxError);});// OK ✅controller(()=>{callSafelySyntax(throwsError);});// ERROR: Call signature thrown types 'Error' and 'CustomSyntaxError' are incompatible. ✅
The last case errors because () => void throws Erroris not assignable to () => void throws CustomSyntaxError (because for e: Error, e could also be thrown as TypeError, RangeError, ReferenceError, ..., due to the fact that these are all subtypes of error).
callSafely(throwsSyntaxError) is OK, because based on my understanding, e instanceof Error will also catch CustomSyntaxError, so it doesn't drop anything.
I don't think it's workable to have different rules for assignability to a variable as to a function arg
I agree on this one – I want things to feel "TS-native" in terms of assignability.
So I think the current implementation now behaves consistently for:
callback parameters
contextual typing
throws never boundaries
assignability
without introducing different rules for variables vs callbacks.
Please let me know if I'm missing some problematic edge case here – this discussion has been extremely useful for clarifying my understanding so far!
n.b. Ethan Resnick (@ethanresnick) - your proposal looks very interesting with UnknownError to account for the unavoidable error cases.
I couldn't implement this as-is in ErrorScript because it would make the throws never case unreachable, which would break the exception checking model. However, there could be some middle ground where everything that doesn't throw never also unions UnknownError, which would force developers to account for unexpected errors whilst keeping useful exception checking behaviour.
adding something like a throws signature (or leaning into a Result<T, E> pattern) sounds great for strict enterprise codebases, but wouldn't it make the learning curve way too steep for everyday React devs?
just wondering if baking this natively into the compiler might tank the type-checking performance. maybe standardizing a Result pattern at the library level first (like how rust does it) could be a safer middle ground?
but wouldn't it make the learning curve way too steep for everyday React devs
I think you're underestimating React devs…
Every one using throw is already internalizing that a function can exit in more than one way. The annotation would only encode that formally. Also, the types would ideally be optional, and inferred from code, just like it happens today with return.
maybe standardizing a Result pattern at the library level first
That can already be done in userland. In fact, it's quite easy to implement using a tagged union; the problem is standardizing it across the whole ecosystem:
Unfortunately, that's not how existing code works, and it's clunky to use without the ? operator, since the throw semantic is to bubble up.
Generating that would be out of scope from TypeScript, since it follows the principle that type annotations should not affect runtime behavior — they exist only at compile time and are erased from the generated JavaScript.
Activity
fatcerberus commentedon Nov 13, 2023
I love the irony that a maintainer was forced to check this box 😄
michaelangeloio commentedon Nov 27, 2023
#13219 (comment)
Kyle Kashuba (@KashubaK) Kyle Venn (@kvenn) I'm going to start looking at intellij (and also see if I can make an eslint plugin for it!). If you want to help, let me know!
kvenn commentedon Nov 27, 2023
Would be happy to be involved. I did like the idea of ESLint plugin because it's piggybacking off of an already established static analysis solution, but I think IDE plugins also check that box.
This comment has some code for the starting point of the ESLint plugin (in a collapsed text block): #13219 (comment)
michaelangeloio commentedon Jan 6, 2024
Kyle Venn (@kvenn) see my comment here about eslint michaelangeloio/does-it-throw#70 (comment)
I've got jetbrains-intellij working now, but waiting on jetbrains to approve it! You can check the code for that here if you'd like: https://github.com/michaelangeloio/does-it-throw/tree/main/jetbrains
kvenn commentedon Jan 7, 2024
Heck yes! I'll happily use the IntelliJ plugin. I'll check back in a bit and install when it's approved.
Shame that eslint doesn't support async and that it's a ways away. But there's even more you can do with a plugin.
Nicely done!
arivera-xealth commentedon Jan 24, 2024
Kyle Venn (@kvenn) jetbrains is now available! https://plugins.jetbrains.com/plugin/23434-does-it-throw-
Feel free to share with others!
kvenn commentedon Jan 29, 2024
I've since gotten the opportunity to try out the JetBrains plugin for does-it-throw and after looking into it a bit more, I don't think that really solves the problem I'm having with exceptions.
That plugin seems to mostly be about alerting of where throw statements are used. Which appears to be for enforcing that you don't use throw statements. I think throw statements are here to stay, even if I agree first class support for errors has advantages. And that if you're already in a codebase which relies on
throws, this adds a lot of noise.I had proposed an ESLint exception to warn when invoking a function that can throw. Encouraging you to either mark(document) this function as one that re-throws or to catch it. With the intention being to prevent you from accidentally having a function that throws bubble up all the way to the top of your program. But allowing that to be the case if it makes sense (like in a GraphQL resolver, where the only way to notify Apollo of the error is via throwing, or a cloud function / queue where throwing is used to retry).
If it can be found implicitly (without documentation), that's better. And it seems like a plugin could actually achieve that (and even offer quick fixes, which would be SO COOL). I'd advocate for using an already used standard TSDoc annotation (@throws) as the acknowledgement that this throw statement is there on purpose (as opposed to introducing a new one -
@it-throwsor@does-it-throw-ignore).does-it-throw has some great bones. And it seems like it's solving a problem for others, it just might not be the right fit for me.
Kashuab commentedon Jan 29, 2024
Kyle Venn (@kvenn) I've tried my hand at an eslint plugin: https://github.com/Kashuab/eslint-plugin-checked-exceptions/tree/main
It introduces two rules:
uncaught-errorsChecks to see if a function you're calling has a
@throwsJSDoc annotation. If you don't wrap the function call in atry/catchit will output an error.undocumented-errorsWarns you if a function has a
throwstatement without a corresponding@throwsannotation. Matches based on what you're throwing, in case you have custom error classes.Check out the tests for examples and what it covers. It's been a while since I looked at this, but I remember it being a bit buggy (i.e. nested branches, complicated logic) so there's a ton of room for improvement. I might take another look to improve it.
Side note - the README suggests you can install it from NPM, this is not the case haha.
(This is my work GH account, dunno why I have a separate one but oh well. I previously contributed here as Kyle Kashuba (@KashubaK))
wiredmatt commentedon Mar 2, 2024
I'm definitely a complete noob when it comes to how Javascript/Typescript works, but would it be possible to add the modifier
throwsto a Typescript function signature?https://docs.oracle.com/javase/tutorial/essential/exceptions/declaring.html
As both a Typescript user and a library maintainer, I hate not being able to consume / deliver proper error declarations. I know I can use JSDoc's
@throws, but having that keyword as part of the function signature would be so great...KashubaK commentedon Mar 2, 2024
Mateo (@wiredmatt) That suggestion was discussed in detail in the linked issue: #13219
The TL;DR is essentially, it's not worth doing because there isn't sufficient existing practice/documentation/runtime behavior to facilitate such a feature. TypeScript is designed to fit within the scope of JS' behavior, and since JavaScript doesn't give us reliable, native tools for things like checked exceptions it's challenging to fit it within scope.
What we're pondering now is, what's the next best thing? How can we encourage better error handling practices enough that the community has some common ground to operate on?
wiredmatt commentedon Mar 2, 2024
Kyle Kashuba (@KashubaK) thank you for your response.
I was thinking about the newly introduced type guards / type predicates, in my mind it seemed totally possible, especially knowing we have conditional types as well.
I'll keep an eye on the eslint solution, that makes sense to me knowing what you just explained. thanks!
mharj commentedon Mar 4, 2024
I was thinking about actual "throws" keyword as optional in return type, so TS would automatically add defaults to current functions/methods .. something like
throws<any>orthrows<unknown>at least for starting point.so when we do write modules we can actually more strictly expose what type of things we are throwing out like example.
and maybe have some utility type similar as typeof to extract errors types from function so we can more easily utilize other modules throw types without directly importing those.
Also maybe this have later impact on catch argument type to actually know throw types, but just actual documentation of throw types is way more important atm for interoperability between modules as currently we are just quessing and reading module source code to undestand what might actually get thrown.
Edit:
this would also work for indication that function will never throw ..
throws<never>RyanCavanaugh commentedon Mar 4, 2024
This doesn't really work unless you have a level of information that doesn't exist in the real world, and requires the ability to express many patterns that are basically arbitrarily complicated. See #13219 (comment)
thw0rted commentedon Mar 4, 2024
If documentation is the issue, you don't need a TS keyword -- https://jsdoc.app/tags-throws has existed for ages. I don't know about you, but I really don't see it used very often. This is the heart of the problem Ryan described in the comment linked above (summarizing the original issue): JS developers don't, broadly speaking, document expected exception behavior, so there's a chicken and egg problem where trying to implement checked-exception types would go against the grain of the current ecosystem.
Use of the
@throwsJSDoc tag can be treated as a sort of demand signal for better exception handling. Poor@throwsadoption indicates that the community doesn't want it. And without good@throwscoverage, athrowskeyword in TS wouldn't be very useful in the best scenario, and would be actively misleading at worst, giving devs the impression that they've handled the "expected" throw scenarios when they haven't.All that said, I still think there could be a place for some limited ability to perform static analysis of exception/rejection handling. I originally found the previous issue when I enabled a linter rule that looks for unhandled Promise rejections, which overlaps with try/catch once
awaitenters the picture. I was looking for a way to decorate some async function calls as being unable to throw or reject (thinkreturn Promise.resolve('static value')), which would let me build out exception-safety from the bottom up, slowly. Maybe this could work if we split the feature into declaring or asserting throws-type (basically the@throwsJSDoc tag), with a separate keyword or directive for enabling exception checking:Note that this is a different scope from what was discussed in the checked-exceptions section of #13219 (comment). I'm trying to statically analyze that explicit/declared
throwspropagate correctly up the chain, and importantly, to limit where those checks are performed. I want to be able to decorate one function as not calling functions with decorated/expected exceptions outside of atryblock -- to annotate one function as "exception-prone" and another as "bad at exception handling". I think there's value in that even if most library functions I call don't (currently) have their expected exceptions documented. (In Ryan's terminology, this requires "option one", unannotated functions are assumed not to throw anything.)88 remaining items
DScheglov commentedon Mar 19, 2025
Kyle Venn (@kvenn)
We are talking here about Typed Exceptions.
The position of TS team is that
unknownis an exact type for exceptions.But really there are cases when we need a typed errors and we need to utilize type system to check that everything is handled correctly.
In TS -- exceptions are not a good way to solve this, considering current ecosystem state. The
Resultis not a unique way to solve that.The solution is simple -- don't throw in non exception cases, and that it. And it is also talking about the exception.
Now it looks like such frameworks like
tRPCuse exceptions ingotostyle -- and then we have arguments here that we need to type errors.Regarding this:
yes, it is not a bullet-proof,
throwsin Java is also not a bullet-proof, because in any moment it could be something like:throws Throwable-- the same asthrows unknown.no, the typo doesn't break anything. Like any typo in the documentation as well. Yes, it will complicate development, but it will not break the program.
snarbles2 commentedon Mar 19, 2025
You really can't explore a problem space without examining the tangential issues. Discussing other solutions and where and when exceptions don't need to be "solved" adds at least as much value as rehashing the same points again for the nth time.
phaux commentedon Mar 19, 2025
The correct way to type
playwould be:So, no. We can't use JSDoc
@throwsin this case, because:Now, if TS was actually checking the throw types it would also tell you there's no such class as NotAllowedError.
That was an excellent example of the quality of the documentation we are gonna get without TS support for checking throw types. Thank you <3
DScheglov commentedon Mar 19, 2025
Niki (@phaux)
yes, you are right, I didn't check the correct type for this cases. It must be just
DOMException:JSDoc doesn't have the
@rejects, but Monaco Editor understands it as well. But it doesn't change the approach, as I said before: any "typo" in documentation doesn't break the program.christopherGdynia commentedon Sep 23, 2025
Hello all,
i have a weird ts behaviour
the error message appears when running tsc
The error happens when adding a check before a type:
In the editor are no errors
Info:
node:v22.17.0yarn:1.22.22typescript: tried both5.8.3&5.9.2As context, error happens in a workspace library which is part of a monorepos with 30 Frontends (NextJS)
I don't know if we have to much type inference or stuff happening but I didn't saw this error message anywhere
I am interested in your thoughts.
Kind Regards Christopher
JamesDHW commentedon May 3, 2026
Hey all - this might be of interest!
I’ve been experimenting with what typed, checked exceptions would look like if they were built into TypeScript.
I know this direction has been previously declined, but I built a prototype anyway as an experiment to explore the trade-offs in practice and how it feels in real code.
Instead of trying to model all possible runtime errors, it only type-checks the exceptions that are part of the program’s declared model - similar to how TypeScript treats types vs
any.The prototype:
// @ts-expect-exception suppressionthrows/rejectsI made a lot of intentional design decisions to make this feel as "TypeScript-native" as possible, but I think there are still some obvious tensions with encouraging exceptions as control flow, which I discussed here.
Playground: https://errorscript.jameshw.dev/playground
ES-demo.mov
I would appreciate any thoughts or feedback 🙏
DScheglov commentedon May 3, 2026
Hey James Haworth Wheatman (@JamesDHW),
Really cool experiment.
I found a case that does not seem to be covered:
ErrorScript Playground
There is no error on the
controllercall, but the callback contract is definitely violated.Minor note: the Playground's "Copy Link" button also seems to generate links with the
/playroute instead of/playground.JamesDHW commentedon May 3, 2026
Hey Dmytro Shchehlov (@DScheglov), thanks for the quick feedback!
I have fixed:
ethanresnick commentedon May 3, 2026
James Haworth Wheatman (@JamesDHW) Love this!
One thing I notice that isn't covered in your docs (and that seems to be at the core of Dmytro Shchehlov (@DScheglov)'s example) is function assignability in the presence of a
throwsclause. So a simpler version of the example would be:The current playground does allow it, and I think that may be the right choice, but it means that the error Dmytro Shchehlov (@DScheglov) is hoping to get is intentionally out of scope.
Also, on the off chance you missed it (because this thread is so freakin' long), I made a similar proposal a while back that might be of interest: #57943
DScheglov commentedon May 4, 2026
Ethan Resnick (@ethanresnick)
I think there are two related but separate questions here:
let-declared function variable is reassigned to afunction with a wider
throwstype?contract says
throws neverorrejects never?For the first case, let's improve your example with a nominal-like custom error type (
Erroris assignable toSyntaxError):Considering that regular TypeScript allows this:
it makes sense to allow thrown exception types to widen for variables declared with
let.However, in this case, after the assignment, the type of
xmust be inferred asfunction (): void throws Errorinstead of the currentfunction (): void throws CustomerSyntaxError.The
controller(cb: () => void throws never)case seems different. It is more similar to this:TS Playground
The
controllercase is important and should not be intentionally excluded from the scope, because declaring an error boundary is one of the motivations for typed exceptions, because, it allows to define a place where developers are forced to handle errors and ensure their code does not throw or reject.For example, an Express-like application could declare its
getmethod so that route handlers must not throw or reject:cc: James Haworth Wheatman (@JamesDHW)
ethanresnick commentedon May 6, 2026
Dmytro Shchehlov (@DScheglov) What about a case like this?
I think you'd say that the inferred throws type of
callSafelyisnever. In that case, this should be legal:However, presumably you would not want this to be legal, because the thrown error will actually not get caught at runtime:
But now — if we stick with the idea from earlier that
x = throwsError;is allowed — then we're saying that one can assignthrowsErrorto a let variable declared/initialized astypeof throwsSyntaxError, but cannot assignthrowsErrorto a function argument with that same type!I don't think it's workable to have different rules for assignability to a variable as to a function arg, etc. If you wanna say that
throwseffects assignability everywhere, then ok, usingthrows nevercan work becausecallSafely(throwsError);just wouldn't be allowed. (Although the TS team has pushed back on error types influencing assignability for other reasons.) But I don't think you can say that assignability depends on whether it's to a callback or a variable.JamesDHW commentedon May 13, 2026
Hey Dmytro Shchehlov (@DScheglov) and Ethan Resnick (@ethanresnick),
Thanks for the support on this - sorry I've taken a while to get back (I've had to think about it)!
I think I understand the concern, and to confirm, in the latest version of ErrorScript, effects are included in function assignability.
TS only allows "widening" if the two types are assignable, so this is OK and expected:
However, this gives an error (and is also expected):
Playground for this.
Since
CustomSyntaxErroris assignable toErrorbut not vice versa.The same applies to effects:
() => void throws CustomSyntaxErroris assignable to() => void throws Errorbut not vice versa.This means we can have code like this:
Playground with implementations.
The last case errors because
() => void throws Erroris not assignable to() => void throws CustomSyntaxError(because fore: Error,ecould also be thrown asTypeError,RangeError,ReferenceError,..., due to the fact that these are all subtypes of error).callSafely(throwsSyntaxError)is OK, because based on my understanding,e instanceof Errorwill also catchCustomSyntaxError, so it doesn't drop anything.I agree on this one – I want things to feel "TS-native" in terms of assignability.
So I think the current implementation now behaves consistently for:
without introducing different rules for variables vs callbacks.
Please let me know if I'm missing some problematic edge case here – this discussion has been extremely useful for clarifying my understanding so far!
n.b. Ethan Resnick (@ethanresnick) - your proposal looks very interesting with
UnknownErrorto account for the unavoidable error cases.I couldn't implement this as-is in ErrorScript because it would make the
throws nevercase unreachable, which would break the exception checking model. However, there could be some middle ground where everything that doesn't throwneveralso unionsUnknownError, which would force developers to account for unexpected errors whilst keeping useful exception checking behaviour.phaux commentedon May 26, 2026
James Haworth Wheatman (@JamesDHW) This is epic
webdevpraveen commentedon Jul 5, 2026
I've been following this thread for a while now.
adding something like a
throwssignature (or leaning into aResult<T, E>pattern) sounds great for strict enterprise codebases, but wouldn't it make the learning curve way too steep for everyday React devs?just wondering if baking this natively into the compiler might tank the type-checking performance. maybe standardizing a
Resultpattern at the library level first (like how rust does it) could be a safer middle ground?felds commentedon Jul 7, 2026
Praveen Kumar Singh (@webdevpraveen)
I think you're underestimating React devs…
Every one using
throwis already internalizing that a function can exit in more than one way. The annotation would only encode that formally. Also, the types would ideally be optional, and inferred from code, just like it happens today withreturn.That can already be done in userland. In fact, it's quite easy to implement using a tagged union; the problem is standardizing it across the whole ecosystem:
Unfortunately, that's not how existing code works, and it's clunky to use without the
?operator, since thethrowsemantic is to bubble up.Generating that would be out of scope from TypeScript, since it follows the principle that type annotations should not affect runtime behavior — they exist only at compile time and are erased from the generated JavaScript.