Skip to content

Talk about Exceptions Here #56365

Description

Acknowledgement

  • I acknowledge that issues using this template may be closed without further explanation at the maintainer's discretion.

Comment

#13219 is locked so that the conclusion doesn't get lost in the discussion, so talk about exceptions here instead

Activity

  1. fatcerberus commented on Nov 13, 2023

    @fatcerberus

    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 😄

  2. michaelangeloio commented on Nov 27, 2023

    @michaelangeloio

    #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!

  3. kvenn commented on Nov 27, 2023

    @kvenn

    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)

  4. michaelangeloio commented on Jan 6, 2024

    @michaelangeloio

    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)

    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

  5. kvenn commented on Jan 7, 2024

    @kvenn

    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!

  6. arivera-xealth commented on Jan 24, 2024

    @arivera-xealth

    Kyle Venn (@kvenn) jetbrains is now available! https://plugins.jetbrains.com/plugin/23434-does-it-throw-

    Feel free to share with others!

  7. kvenn commented on Jan 29, 2024

    @kvenn

    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.

  8. Kashuab commented on Jan 29, 2024

    @Kashuab

    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-errors

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

  9. wiredmatt commented on Mar 2, 2024

    @wiredmatt

    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?

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

  10. KashubaK commented on Mar 2, 2024

    @KashubaK

    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?

  11. wiredmatt commented on Mar 2, 2024

    @wiredmatt

    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!

  12. mharj commented on Mar 4, 2024

    @mharj

    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.

    function doAuth(): AuthPayload throws<TypeError | AuthError> {}

    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.

    function someStuff(): AuthPayload throws<throwof doAuth | FatalError> {}

    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>

  13. RyanCavanaugh commented on Mar 4, 2024

    @RyanCavanaugh
    MemberAuthor

    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)

  14. thw0rted commented on Mar 4, 2024

    @thw0rted

    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 */
    function throwsSometimes(): number {
      if (Math.random() < 0.5) { throw 'nope!'; }
      return (Math.random() < 0.5) ? 0 : 1;
    }
    
    /** unsafe-assertion-that-this-throws-never */
    function throwsNever(): number { return JSON.parse('2'); }
    
    /** checked-assertion-that-this-throws-never */
    function maybeSafe(): number {
      return throwsSometimes() || 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/declared throws 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.)

  15. 88 remaining items

  16. DScheglov commented on Mar 19, 2025

    @DScheglov

    Kyle Venn (@kvenn)

    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.

  17. snarbles2 commented on Mar 19, 2025

    @snarbles2

    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.

  18. phaux commented on Mar 19, 2025

    @phaux

    See it in the TS Playground.

    The correct way to type play would be:

    interface HTMLVideoElement {
      play(): Promise<void, DOMException<"NotAllowedError" | "NotSupportedError">>
    }

    So, no. We can't use JSDoc @throws in this case, because:

    1. It doesn't throw. It returns a promise which rejects.
    2. You can't add error type to Promise. TS will error that it only takes 1 generic param.
    3. Even if it wasn't a Promise but a regular throw it would be ignored by TS anyways.

    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

  19. DScheglov commented on Mar 19, 2025

    @DScheglov

    Niki (@phaux)

    interface HTMLVideoElement {
      play(): Promise<void, DOMException<"NotAllowedError" | "NotSupportedError">>
    }

    yes, you are right, I didn't check the correct type for this cases. It must be just DOMException:

    interface HTMLVideoElement {
      /**
       * 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.

  20. christopherGdynia commented on Sep 23, 2025

    @christopherGdynia

    Hello all,

    i have a weird ts behaviour

    the error message appears when running tsc

    ~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

  21. JamesDHW commented on May 3, 2026

    @JamesDHW

    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:

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

    Playground: https://errorscript.jameshw.dev/playground

    ES-demo.mov

    I would appreciate any thoughts or feedback 🙏

  22. DScheglov commented on May 3, 2026

    @DScheglov

    Hey James Haworth Wheatman (@JamesDHW),

    Really cool experiment.

    I found a case that does not seem to be covered:

    declare function throwsTypeError(): void throws TypeError;
    declare function throwsError(): void throws Error;
    
    declare function controller(callback: () => void throws never): void;
    
    controller(() => {
        throwsTypeError();
        throwsError();
    });

    ErrorScript Playground

    There is no error on the controller call, but the callback contract is definitely violated.

    Minor note: the Playground's "Copy Link" button also seems to generate links with the /play route instead of /playground.

  23. JamesDHW commented on May 3, 2026

    @JamesDHW

    Hey Dmytro Shchehlov (@DScheglov), thanks for the quick feedback!

    I have fixed:

    Image
  24. ethanresnick commented on May 3, 2026

    @ethanresnick
    Contributor

    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 throws clause. So a simpler version of the example would be:

    declare function throwsError(): void throws Error;
    declare function throwsSyntaxError(): void throws SyntaxError;
    
    let x = 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

  25. DScheglov commented on May 4, 2026

    @DScheglov

    Ethan Resnick (@ethanresnick)

    I think there are two related but separate questions here:

    1. What should happen when a let-declared function variable is reassigned to a
      function with a wider throws type?
    2. 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):

    class CustomerSyntaxError extends Error {
      name = "CustomerSyntaxError" as const;
    }
    
    declare function throwsError(): void throws Error;
    declare function throwsSyntaxError(): void throws CustomerSyntaxError;
    
    const e: CustomerSyntaxError = new Error();
    // (2322) Type 'Error' is not assignable to type 'CustomerSyntaxError'.
    
    let x = throwsSyntaxError;
    x = throwsError;

    Considering that regular TypeScript allows this:

    let x: 10 | 20 = 10; // x: 10 | 20
    x += 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:

    declare function fn(y: 10 | 20): void;
    
    let y: 10 | 20 = 20;
    
    fn(y + 1);
    // ~~~~~
    // (2345) Argument of type 'number' is not assignable to parameter of type '10 | 20'.

    TS Playground

    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:

    interface Application {
      get(
        path: string,
        handler: {
          (req: Request, res: Response, next: NextFunction): void
            throws never;
          (req: Request, res: Response, next: NextFunction): Promise<void>
            throws never
            rejects never;
        },
      ): void;
    }
    
    declare function parseUuid(input: string): input is UUIDString
      throws UUIDParsingError;
    declare function findUser(id: string): Promise<User>
      rejects UserNotFoundError;
    
    app.get("/users/:id", async (req, res) => {
      const userId = parseUuid(req.params.id);
      //             ~~~~~~~~~
      // The handler violates the `rejects never` contract
    
      const user = await findUser(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);
    });

    cc: James Haworth Wheatman (@JamesDHW)

  26. ethanresnick commented on May 6, 2026

    @ethanresnick
    Contributor

    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.

    Dmytro Shchehlov (@DScheglov) What about a case like this?

    declare function throwsError(): void throws Error;
    declare function throwsSyntaxError(): void throws CustomerSyntaxError;
    
    function callSafely(cb: () => void throws CustomerSyntaxError): void {
      try { 
        cb(); 
      } catch(e) { 
        if(e instanceof CustomerSyntaxError) { 
          return fallback; 
        }
        throw e;
      }
    }

    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.

  27. JamesDHW commented on May 13, 2026

    @JamesDHW

    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:

    let num: 10 | 20 = 10; // x: 10 | 20
    num += 1; // x: number ✅

    However, this gives an error (and is also expected):

    class CustomSyntaxError extends Error {
      name = "CustomSyntaxError" as const;
    }
    
    const e1: Error = new CustomSyntaxError(); // OK ✅
    const e2: CustomSyntaxError = new Error(); // ERROR: (2322) Type 'Error' is not assignable to type 'CustomSyntaxError'. ✅

    Playground for this.

    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:

    class CustomSyntaxError extends Error {
      name = "CustomSyntaxError" as const;
    }
    
    declare function controller(callback: () => void throws never): void;
    
    declare function throwsError(): void throws Error;
    declare function throwsSyntaxError(): void throws CustomSyntaxError;
    
    function callSafely(cb: () => void throws Error): void {
      try { 
        cb(); 
      } catch(e) { 
        if(e instanceof Error) { // ✅ changed impl here such that the inferred type of `callSafely` is `throws never`.
          return; 
        }
        throw e;
      }
    }
    
    function callSafelySyntax(cb: () => void throws CustomSyntaxError): void {
      try { 
        cb(); 
      } catch(e) { 
        if(e instanceof CustomSyntaxError) { 
          return; 
        }
        throw e;
      }
    }
    
    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. ✅

    Playground with implementations.

    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.

  28. phaux commented on May 26, 2026

    @phaux
  29. webdevpraveen commented on Jul 5, 2026

    @webdevpraveen

    I've been following this thread for a while now.

    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?

  30. felds commented on Jul 7, 2026

    @felds

    Praveen Kumar Singh (@webdevpraveen)

    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:

    type Result<T, E> =
      | { ok: true; value: T }
      | { ok: false; error: E };

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DiscussionIssues which may not have code impact

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions