https://github.com/microsoft/TypeScript/issues/13219 Skip to content Toggle navigation Sign up * Product + Actions Automate any workflow + Packages Host and manage packages + Security Find and fix vulnerabilities + Codespaces Instant dev environments + Copilot Write better code with AI + Code review Manage code changes + Issues Plan and track work + Discussions Collaborate outside of code Explore + All features + Documentation + GitHub Skills + Blog * Solutions For + Enterprise + Teams + Startups + Education By Solution + CI/CD & Automation + DevOps + DevSecOps Resources + Customer Stories + White papers, Ebooks, Webinars + Partners * Open Source + GitHub Sponsors Fund open source developers + The ReadME Project GitHub community articles Repositories + Topics + Trending + Collections * Pricing Search or jump to... Search code, repositories, users, issues, pull requests... Search [ ] Clear Search syntax tips Provide feedback We read every piece of feedback, and take your input very seriously. [ ] [ ] Include my email address so I can be contacted Cancel Submit feedback Saved searches Use saved searches to filter your results more quickly Name [ ] Query [ ] To see all available qualifiers, see our documentation. Cancel Create saved search Sign in Sign up 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. {{ message }} microsoft / TypeScript Public * Notifications * Fork 12.1k * Star 93.6k * Code * Issues 5k+ * Pull requests 338 * Actions * Projects 8 * Wiki * Security * Insights More * Code * Issues * Pull requests * Actions * Projects * Wiki * Security * Insights New issue Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community. Pick a username [ ] Email Address [ ] Password [ ] [ ] Sign up for GitHub By clicking "Sign up for GitHub", you agree to our terms of service and privacy statement. We'll occasionally send you account related emails. Already on GitHub? Sign in to your account Jump to bottom Suggestion: throws clause and typed catch clause #13219 Closed nitzantomer opened this issue Dec 29, 2016 * 269 comments * Fixed by microsoft/workspace-tools#86 Closed Suggestion: throws clause and typed catch clause #13219 nitzantomer opened this issue Dec 29, 2016 * 269 comments * Fixed by microsoft/workspace-tools#86 Labels Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript Comments @nitzantomer Copy link nitzantomer commented Dec 29, 2016 The typescript type system is helpful in most cases, but it can't be utilized when handling exceptions. For example: function fn(num: number): void { if (num === 0) { throw "error: can't deal with 0"; } } The problem here is two fold (without looking through the code): 1. When using this function there's no way to know that it might throw an error 2. It's not clear what the type(s) of the error is going to be In many scenarios these aren't really a problem, but knowing whether a function/method might throw an exception can be very useful in different scenarios, especially when using different libraries. By introducing (optional) checked exception the type system can be utilized for exception handling. I know that checked exceptions isn't agreed upon (for example Anders Hejlsberg), but by making it optional (and maybe inferred? more later) then it just adds the opportunity to add more information about the code which can help developers, tools and documentation. It will also allow a better usage of meaningful custom errors for large big projects. As all javascript runtime errors are of type Error (or extending types such as TypeError) the actual type for a function will always be type | Error. The grammar is straightforward, a function definition can end with a throws clause followed by a type: function fn() throws string { ... } function fn(...) throws string | number { ... } class MyError extends Error { ... } function fn(...): Promise throws MyError { ... } When catching the exceptions the syntax is the same with the ability to declare the type(s) of the error: catch(e: string | Error) { ... } Examples: function fn(num: number): void throws string { if (num === 0) { throw "error: can't deal with 0"; } } Here it's clear that the function can throw an error and that the error will be a string, and so when calling this method the developer (and the compiler/IDE) is aware of it and can handle it better. So: fn(0); // or try { fn(0); } catch (e: string) { ... } Compiles with no errors, but: try { fn(0); } catch (e: number) { ... } Fails to compile because number isn't string. Control flow and error type inference try { fn(0); } catch(e) { if (typeof e === "string") { console.log(e.length); } else if (e instanceof Error) { console.log(e.message); } else if (typeof e === "string") { console.log(e * 3); // error: Unreachable code detected } console.log(e * 3); // error: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type } function fn(num: number): void { if (num === 0) { throw "error: can't deal with 0"; } } Throws string. function fn2(num: number) { if (num < 0) { throw new MyError("can only deal with positives"); } fn(num); } Throws MyError | string. However: function fn2(num: number) { if (num < 0) { throw new MyError("can only deal with positives"); } try { fn(num); } catch(e) { if (typeof e === "string") { throw new MyError(e); } } } Throws only MyError. The text was updated successfully, but these errors were encountered: 1421 Asele, edevine, bcherny, prashbh, shaipetel, terbooter, MrKomish, ebaersoft, cojack, karol-depka, and 1411 more reacted with thumbs up emoji 16 zpdDG4gta8XKpMCd, azz, aluanhaddad, jwbay, mkusher, ToporDon, radiosterne, jgbpercy, kyler-hyuna, JCMais, and 6 more reacted with thumbs down emoji 17 shazgames1, JCMais, Lama06, ammanvedi, marcin-wlodarczyk, Togira123, guilhermelMoraes, flowstate247, erdkse, flugg, and 7 more reacted with laugh emoji 41 johndatserakis, lgenzelis, kyler-hyuna, shazgames1, JCMais, alexisrougnant, sushruth, GabeAtWork, Lama06, ammanvedi, and 31 more reacted with hooray emoji [?] 222 lordazzi, yss14, LucasPaganini, IliaVolk, trusktr, jdolle, xealot, getDanArias, armand1m, SamVerschueren, and 212 more reacted with heart emoji 93 TacB0sS, bali182, Blaumaus, vojvodics, quentez, pranavjindal999, kirillbogdanov, eczn, p0vidl0, zero-t4, and 83 more reacted with rocket emoji 81 nicko466, Blaumaus, pranavjindal999, Faithful-Mind, kirillbogdanov, bduff9, Nkmol, p0vidl0, zero-t4, ivawzh, and 71 more reacted with eyes emoji All reactions * 1421 reactions * 16 reactions * 17 reactions * 41 reactions * [?] 222 reactions * 93 reactions * 81 reactions @DanielRosenwasser DanielRosenwasser changed the title [DEL: Suggestion: Checked exceptions and typed cache clause:DEL] [INS: Suggestion: Checked exceptions and typed catch clause:INS] Dec 29, 2016 @DanielRosenwasser DanielRosenwasser added the Suggestion An idea for TypeScript label Dec 30, 2016 @DanielRosenwasser Copy link Member DanielRosenwasser commented Dec 30, 2016 Just to clarify - one the ideas here is not to force users to catch the exception, but rather, to better infer the type of a catch clause variable? 82 ExE-Boss, ivawzh, Finbel, resynth1943, ccorcos, benjamin-rood, luisgurmendezMLabs, thomasgauvin, dannysood, ninofiliu, and 72 more reacted with thumbs up emoji All reactions * 82 reactions Sorry, something went wrong. @DanielRosenwasser DanielRosenwasser added the In Discussion Not yet reached consensus label Dec 30, 2016 @nitzantomer Copy link Author nitzantomer commented Dec 30, 2016 * edited @DanielRosenwasser Yes, users won't be forced to catch exceptions, so this is fine with the compiler (at runtime the error is thrown of course): function fn() { throw "error"; } fn(); // and try { fn(); } finally { // do something here } But it will give developers a way to express which exceptions can be thrown (would be awesome to have that when using other libraries .d.ts files) and then have the compiler type guard the exception types inside the catch clause. 73 felixfbecker, trusktr, RGCsAGupta, rasmusgo, lukas-tr, senyaak, mrGibi, Jezorko, r-k-b, codahk, and 63 more reacted with thumbs up emoji [?] 19 trusktr, FedeBev, variousauthors, codahk, Finbel, thomasgauvin, 12kb, uladkasach, GabeAtWork, peey, and 9 more reacted with heart emoji All reactions * 73 reactions * [?] 19 reactions Sorry, something went wrong. @RyanCavanaugh RyanCavanaugh mentioned this issue Jan 9, 2017 Suggestion Backlog Slog, 1/2/2017 #13368 Closed @zpdDG4gta8XKpMCd Copy link zpdDG4gta8XKpMCd commented Jan 9, 2017 * edited how is a checked throw different from Tried? type Tried = Success | Failure; interface Success { kind: 'result', result: Result } interface Failure { kind: 'failure', error: Error } function isSuccess(tried: Tried): tried is Success { return tried.kind === 'result'; } function mightFail(): Tried { } const tried = mightFail(); if (isSuccess(tried)) { console.log(tried.success); } else { console.error(tried.error); } instead of try { const result: Result = mightFail(); console.log(success); } catch (error: Error) { console.error(error); } 13 dwickstrom, raveclassic, Jezorko, antonpetkoff, mrGibi, diekmrcoin, jgbpercy, aperkaz, DanKaplanSES, magnus-allison, and 3 more reacted with thumbs up emoji 161 Asele, jesseschalken, ebaersoft, cojack, bartzilla, sjkillen, cocowalla, leonadler, JoseLuisGarciaOtt, wub, and 151 more reacted with thumbs down emoji 1 ognjen-andric reacted with laugh emoji 15 trusktr, WilhelmOlejnik, naishe, talentumtuum, jsejcksn, HoldYourWaffle, christopher-francisco, matthewjh, MatthiasEngh, 12kb, and 5 more reacted with confused emoji All reactions * 13 reactions * 161 reactions * 1 reaction * 15 reactions Sorry, something went wrong. @RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature and removed In Discussion Not yet reached consensus labels Jan 10, 2017 @nitzantomer Copy link Author nitzantomer commented Jan 10, 2017 @Aleksey-Bykov You're suggesting not to use throw at all in my code and instead wrap the results (in functions that might error). This approach has a few drawbacks: * This wrapping creates more code * It requires that all chain of invoked function return this wrapped value (or error) or alternatively the function that gets Tried<> can not choose to ignore the error. * It is not a standard, 3rd party libraries and the native js throw errors Adding throws will enable developers who choose to to handle errors from their code, 3rd libraries and native js. As the suggestion also requests for error inferring, all generated definition files can include the throws clause. It will be very convenient to know what errors a function might throw straight from the definition file instead of the current state where you need to go to the docs, for example to know which error JSON.parse might throw I need to go to the MDN page and read that: Throws a SyntaxError exception if the string to parse is not valid JSON And this is the good case when the error is documented. 149 bcherny, lddubeau, cocowalla, arciisine, BadgerBadgerBadgerBadger, agibalov, janis91, wkronemeijer, sunilpes, max-holmark, and 139 more reacted with thumbs up emoji [?] 15 codexpansion, luisgurmendezMLabs, pedrotorchio, goldingdamien, 12kb, uladkasach, cpyle0819, josh-hemphill, aleksamarkoni, zimmermanw84, and 5 more reacted with heart emoji All reactions * 149 reactions * [?] 15 reactions Sorry, something went wrong. @zpdDG4gta8XKpMCd Copy link zpdDG4gta8XKpMCd commented Jan 10, 2017 * edited And this is the good case when the error is documented. is there a reliable way in javascript to tell apart SyntaxError from Error? * yes, it's more code, but since a bad situation is represented in an object, it can be passed around to be processed, discarded, stored or transformed into a valid result just like any other value * you can ignore tried by returning tried too, tried can be viewed as a monad, look for monadic computations function mightFail(): Tried { } function mightFailToo(): Tried { const tried = mightFail(); if (isSuccess(tried)) { return successFrom(tried.result * 2); } else { return tried; } } * it's standard enough for your code, when it comes to 3rd party libs throwing an exception it generally means a gameover for you, because it is close to impossible to reliably recover from an exception, reason is that it can be thrown from anywhere inside the code terminating it at an arbitrary position and leaving its internal state incomplete or corrupt * there is no support for checked exceptions from JavaScript runtime, and i am afraid it cannot be implemented in typescript alone other than that encoding an exception as a special result case is a very common practice in FP world whereas splitting a possible outcome into 2 parts: * one delivered by the return statement and * another delivered by throw looks a made up difficulty in my opinion, throw is good for failing fast and loud when nothing you can do about it, explicitly coded results are good for anything that implies a bad yet expected situation which you can recover from 9 kgajowy, antonpetkoff, steveofficer, AlaaMouch, amccall-eigt, bit-twit, mooman219, dominikbucher, and rperryng reacted with thumbs up emoji 46 JoshuaKGoldberg, senyaak, kmontag, gabrielrinaldi, simeyla, bertyhell, patrickroberts, ssilve1989, someniatko, Herriau, and 36 more reacted with thumbs down emoji All reactions * 9 reactions * 46 reactions Sorry, something went wrong. @zpdDG4gta8XKpMCd Copy link zpdDG4gta8XKpMCd commented Jan 10, 2017 * edited consider: // throw/catch declare function doThis(): number throws string; declare function doThat(): number throws string; function doSomething(): number throws string { let oneResult: number | undefined = undefined; try { oneResult = doThis(); } catch (e) { throw e; } let anotherResult: number | undefined = undefined; try { anotherResult = doThat(); } catch (e) { throw e; } return oneResult + anotherResult; } // explicit results declare function doThis(): Tried; declare function doThat(): Tried; function withBothTried(one: Tried, another: Tried, haveBoth: (one: T, another: T) => R): Tried { return isSuccess(one) ? isSuccess(another) ? successFrom(haveBoth(one.result, another.result)) : another : one; } function add(one: number, another: number) { return one + another; } function doSomething(): Tried { return withBothTried( doThis(), doThat(), add ); } 1 val-o reacted with thumbs up emoji 5 anacierdem, iamcsharper, TheNickmaster21, ErvinRacz, and TomasHubelbauer reacted with thumbs down emoji 4 parzhitsky, reinik21, PindaPixel, and ericbf reacted with confused emoji All reactions * 1 reaction * 5 reactions * 4 reactions Sorry, something went wrong. @nitzantomer Copy link Author nitzantomer commented Jan 10, 2017 * edited @Aleksey-Bykov My point with JSON.parse might throwing SyntaxError is that I need to look the function up in the docs just to know that it might throw, and it would be easier to see that in the .d.ts. And yes, you can know that it's SyntaxError with using instanceof. You can represent the same bad situation with throwing an error. You can create your own error class which extends Error and put all of the relevant data that you need in it. You're getting the same with less code. Sometimes you have a long chain of function invocations and you might want to deal with some of the errors in different levels of the chain. It will be pretty annoying to always use wrapped results (monads). Not to mention that again, other libraries and native errors might be thrown anyway, so you might end up using both monads and try/catch. I disagree with you, in a lot of cases you can recover from thrown errors, and if the language lets you express it better than it will be easier to do so. Like with a lot of things in typescript, the lack of support of the feature in javascript isn't an issue. This: try { mightFail(); } catch (e: MyError | string) { if (e instanceof MyError) { ... } else if (typeof e === "string") { ... } else {} } Will work as expected in javascript, just without the type annotation. Using throw is enough to express what you're saying: if the operation succeeded return the value, otherwise throw an error. The user of this function will then decide if he wants to deal with the possible errors or ignore them. You can deal with only errors you thrown yourself and ignore the ones which are 3rd party for example. 36 ebaersoft, leonadler, thunder033, BadgerBadgerBadgerBadger, janis91, MarvinHannott, wkronemeijer, felixfbecker, jpidelatorre, Ranguna, and 26 more reacted with thumbs up emoji [?] 2 cpyle0819 and bernardoduarte reacted with heart emoji All reactions * 36 reactions * [?] 2 reactions Sorry, something went wrong. @zpdDG4gta8XKpMCd Copy link zpdDG4gta8XKpMCd commented Jan 10, 2017 if we talking about browsers instanceof is only good for stuff that originates from the same window/document, try it: var child = window.open('about:blank'); console.log(child.Error === window.Error); so when you do: try { child.doSomething(); } catch (e) { if (e instanceof SyntaxError) { } } you won't catch it another problem with exceptions that they might slip into your code from far beyond of where you expect them to happen try { doSomething(); // <-- uses 3rd party library that by coincidence throws SyntaxError too, but you don' t know it } catch (e) {} 15 aluanhaddad, ethanresnick, omril1, Eoksni, ExE-Boss, jsejcksn, ackvf, theScottyJam, ArturBaybulatov, AlaaMouch, and 5 more reacted with thumbs up emoji 12 wkronemeijer, gitowiec, JoshuaKGoldberg, Herriau, danbulant, tommytroylin, kryptamine, stewartmcgown, BalaM314, reinik21, and 2 more reacted with thumbs down emoji All reactions * 15 reactions * 12 reactions Sorry, something went wrong. @zpdDG4gta8XKpMCd Copy link zpdDG4gta8XKpMCd commented Jan 10, 2017 * edited besides instanceof is vulnerable to prototype inheritance, so you need to be extra cautions to always check against the final ancestor class StandardError {} class CustomError extends StandardError { } function doSomething() { throw new CustomError(); } function oldCode() { try { doSomething(); } catch (e) { if (e instanceof StandardError) { // problem } } } 2 aluanhaddad and kgajowy reacted with thumbs up emoji 14 wkronemeijer, gitowiec, JoshuaKGoldberg, WilhelmOlejnik, Herriau, danbulant, derek-pavao, tommytroylin, stewartmcgown, BalaM314, and 4 more reacted with thumbs down emoji All reactions * 2 reactions * 14 reactions Sorry, something went wrong. @gcnew Copy link Contributor gcnew commented Jan 10, 2017 * edited @Aleksey-Bykov Explicitly threading errors as you suggest in monadic structures is quite hard and daunting task. It takes a lot of effort, makes the code hard to understand and requires language support / type-driven emit to be on the edge of being bearable. This is a comment comming from somebody who puts a lot of effort into popularising Haskell and FP as a whole. It is a working alternative, especially for enthusiasts (myself included), however I don't think it's a viable option for the larger audience. 42 JoseLuisGarciaOtt, wub, aluanhaddad, felixfbecker, anup-the-magic, wkronemeijer, ethanresnick, interphx, armand1m, dsabanin, and 32 more reacted with thumbs up emoji [?] 4 codexpansion, 12kb, uladkasach, and aleksamarkoni reacted with heart emoji All reactions * 42 reactions * [?] 4 reactions Sorry, something went wrong. @aluanhaddad Copy link Contributor aluanhaddad commented Jan 10, 2017 Actually, my main concern here is that people will start subclassing Error. I think this is a terrible pattern. More generally, anything that promotes the use of the instanceof operator is just going to create additional confusion around classes. 9 RyanCavanaugh, gcnew, Horaddrim, mheiber, variousauthors, jgbpercy, dominikbucher, reverofevil, and timbuckley reacted with thumbs up emoji 44 trusktr, armand1m, gitowiec, Jamesernator, Eoksni, ajxs, sebinsua, Floby, dean-epic, thomasgauvin, and 34 more reacted with thumbs down emoji 2 trusktr and JemarJones reacted with confused emoji All reactions * 9 reactions * 44 reactions * 2 reactions Sorry, something went wrong. @zpdDG4gta8XKpMCd Copy link zpdDG4gta8XKpMCd commented Jan 10, 2017 * edited This is a comment comming from somebody who puts a lot of effort into popularising Haskell and FP as a whole. i really think this should be pushed harder to the audience, not until it's digested and asked for more can we have better FP support in the language and it's not as daunting as you think, provided all combinators are written already, just use them to build a data flow, like we do in our project, but i agree that TS could have supported it better: # 2319 1 aluanhaddad reacted with thumbs up emoji 8 JoshuaKGoldberg, 12kb, nathggns, domarmstrong, BalaM314, johncmunson, TomasHubelbauer, and tristan-mastrodicasa reacted with thumbs down emoji All reactions * 1 reaction * 8 reactions Sorry, something went wrong. @gcnew Copy link Contributor gcnew commented Jan 10, 2017 * edited Monad transformers are a real PITA. You need lifting, hoisting and selective running fairly often. The end result is hardly comprehendible code and much higher than needed barrier of entry. All the combinators and lifting functions (which provide the obligatory boxing/unboxing) are just noise distracting you from the problem at hand. I do believe that being explicit about state, effects, etc is a good thing, but I don't think we have found a convenient wrapping / abstraction yet. Until we find it, supporting traditional programming patterns seems like the way to go without stopping to experiment and explore in the mean time. PS: I think we need more than custom operators. Higher Kinded Types and some sort of type classes are also essential for a practical monadic library. Among them I'd rate HKT first and type classes a close second. With all that said, I believe TypeScript is not the language for practicing such concepts. Toying around - yes, but its philosophy and roots are fundamentally distant for a proper seamless integration. 23 aluanhaddad, interphx, felixfbecker, gitowiec, dsabanin, antonpetkoff, mailmindlin, pietschy, 12kb, pixyj, and 13 more reacted with thumbs up emoji All reactions * 23 reactions Sorry, something went wrong. @gcnew Copy link Contributor gcnew commented Jan 10, 2017 Back to the OP question - instanceof is a dangerous operator to use. However explicit exceptions are not limited to Error. You can throw your own ADTs or custom POJO errors as well. The proposed feature can be quite useful and, of course, can also be misused pretty hard. In any case it makes functions more transparent which is undoubtedly a good thing. As a whole I'm 50/50 on it :) 9 aluanhaddad, wkronemeijer, ethanresnick, gitowiec, OhDavit, hrajchert, Vinnl, cpyle0819, and patrickJramos reacted with thumbs up emoji All reactions * 9 reactions Sorry, something went wrong. @nitzantomer Copy link Author nitzantomer commented Jan 10, 2017 @Aleksey-Bykov Developers should be aware of the different js issues you described, after all adding throws to typescript doesn't introduce anything new to js, it only gives typescript as a language the ability to express an existing js behavior. The fact that 3rd party libraries ca throw errors is exactly my point. If their definition files were to include that then I will have a way to know it. @aluanhaddad Why is it a terrible pattern to extend Error? @gcnew As for instanceof, that was just an example, I can always throw regular objects which have different types and then use type guards to differentiate between them. It will be up to the developer to decide what type of errors he wishes to throw, and it probably is the case already, but currently there's no way to express that, which is what this suggestion wants to solve. 32 felixfbecker, trusktr, gitowiec, hrajchert, dggluz, MobiusHorizons, mailmindlin, DanielHreben, thomasgauvin, harryjamesuk, and 22 more reacted with thumbs up emoji [?] 5 trusktr, gitowiec, dggluz, aleksamarkoni, and riderx reacted with heart emoji All reactions * 32 reactions * [?] 5 reactions Sorry, something went wrong. @gcnew Copy link Contributor gcnew commented Jan 10, 2017 * edited @nitzantomer Subclassing native classes (Error, Array, RegExp, etc) was not supported in older ECMAScript versions (prior to ES6). The down level emit for these classes gives unexpected results (best effort is made but this is as far as one can go) and is the reason for numerous issues logged on daily basis. As a rule of thumb - don't subclass natives unless you are targeting recent ECMAScript versions and really know what you are doing. 5 aluanhaddad, janis91, gitowiec, minecrawler, and TheNickmaster21 reacted with thumbs up emoji All reactions * 5 reactions Sorry, something went wrong. @nitzantomer Copy link Author nitzantomer commented Jan 10, 2017 @gcnew Oh, I'm well aware of that as I spent more than a few hours trying to figure out what went wrong. But with the ability to do so now there shouldn't be a reason not to (when targeting es6). In anycase this suggestion doesn't assume that the user is subclassing the Error class, it was just an example. All reactions Sorry, something went wrong. @gcnew Copy link Contributor gcnew commented Jan 11, 2017 * edited @nitzantomer I'm not arguing that the suggestion is limited to Error. I just explained why it's a bad pattern to subclass it. In my post I actually defended the stance that custom objects or discriminated unions may be used as well. instanceof is dangerous and considered an anti-pattern even if you take out the specificities of JavaScript - e.g. Beware of instanceof operator. The reason is that the compiler cannot protect you against bugs introduced by new subclasses. Logic using instanceof is fragile and does not follow the open/closed principle, as it expects only a handful of options. Even if a wildcard case is added, new derivates are still likely to cause errors as they may break assumptions made at the time of writing. For the cases where you want to distinguish among known alternatives TypeScript has Tagged Unions (also called discriminated unions or algebraic data types). The compiler makes sure that all cases are handled which gives you nice guarantees. The downside is that if you want to add a new entry to the type, you'll have to go through all the code discriminating on it and handle the newly added option. The upside is that such code would have most-likely been broken, but would have failed at runtime. 1 aluanhaddad reacted with thumbs up emoji 3 wkronemeijer, yGuy, and zardoy reacted with thumbs down emoji All reactions * 1 reaction * 3 reactions Sorry, something went wrong. @gcnew Copy link Contributor gcnew commented Jan 11, 2017 * edited I just gave this proposal a second thought and became against it. The reason is that if throws declarations were present on signatures but were not enforced, they can already be handled by documentation comments. In the case of being enforced, I share the sentiment that they'd become irritating and swallowed fast as JavaScript lacks Java's mechanism for typed catch clauses. Using exceptions (especially as control flow) has never been an established practice as well. All of this leads me to the understanding that checked exceptions bring too little, while better and presently more common ways to represent failure are available (e.g. union return). 2 aluanhaddad and bit-twit reacted with thumbs up emoji 6 BalaM314, dennishylau, 2huBrulee, zardoy, MidnightDesign, and ericbf reacted with thumbs down emoji 1 zpdDG4gta8XKpMCd reacted with laugh emoji All reactions * 2 reactions * 6 reactions * 1 reaction Sorry, something went wrong. @nitzantomer Copy link Author nitzantomer commented Jan 11, 2017 * edited @gcnew This is how it's done in C#, the problem is that docs aren't as standard in typescript. I do not remember coming across a definition file which is well documented. The different lib.d.ts files do contain comments, but those do not contain thrown errors (with one exception: lib.es6.d.ts has one throws in Date[Symbol.toPrimitive](hint: string)). Also, this suggestion takes error inferring into account, something that won't happen if errors are coming from documentation comments. With inferred checked exceptions the developer won't even need to specify the throws clause, the compiler will infer it automatically and will use it for compilation and will add it to the resulting definition file. I agree that enforcing error handling isn't a good thing, but having this feature will just add more information which can be then used by those who wish to. The problem with: ... there are better and presently more common ways to represent failure Is that there's no standard way of doing it. You might use union return, @Aleksey-Bykov will use Tried<>, and a developer of another 3rd party library will do something completely different. Throwing errors is a standard across languages (js, java, c#...) and as it's part of the system and not a workaround, it should (in my opinion) have better handling in typescript, and a proof of that is the number of issues I've seen here over time which ask for type annotation in the catch clause. 36 trusktr, rasmusgo, bali182, hrajchert, kylecorbelli, max-loginov, mailmindlin, avik-so, antonagestam, Griffork, and 26 more reacted with thumbs up emoji All reactions * 36 reactions Sorry, something went wrong. @HolgerJeromin Copy link Contributor HolgerJeromin commented Jan 11, 2017 I would love to have information in the tooltip in VS if a function (or called function) can throw. For *.d.ts files we probably need a fake parameter like this since TS2.0. 6 yss14, mrGibi, uladkasach, riderx, lobsterkatie, and patrickJramos reacted with thumbs up emoji All reactions * 6 reactions Sorry, something went wrong. @nitzantomer Copy link Author nitzantomer commented Jan 11, 2017 @HolgerJeromin Why would it be needed? 2 HolgerJeromin and aluanhaddad reacted with thumbs up emoji All reactions * 2 reactions Sorry, something went wrong. @zpdDG4gta8XKpMCd Copy link zpdDG4gta8XKpMCd commented Jan 11, 2017 * edited here is a simple question, what signature should be inferred for dontCare in the code below? function mightThrow(): void throws string { if (Math.random() > 0.5) { throw 'hey!'; } } function dontCare() { return mightThrow(); } according to what you said in your proposal it should be function dontCare(): void throws string { i say it should be a type error since a checked exception wasn't properly handled function dontCare() { // <-- Checked exception wasn't handled. ^^^^^^^^^^ why is that? because otherwise there is a very good chance of getting the state of the immediate caller corrupt: class MyClass { private values: number[] = []; keepAllValues(values: number[]) { for (let index = 0; index < values.length; index ++) { this.values.push(values[index]); mightThrow(); } } } if you let an exception to slip through you can not infer it as checked, because the behavior contract of keepAllValues would be violated this way (not all values were kept despite the original intent) the only safe way to is catch them immediately and rethrow them explicitly keepAllValues(values: number[]) { for (let index = 0; index < values.length; index ++) { this.values.push(values[index]); try { mightThrow(); } catch (e) { // the state of MyClass is going to be corrupt anyway // but unlike the other example this is a deliberate choice throw e; } } } otherwise despite the callers know what can be trown you can't give them guarantees that it's safe to proceed using code that just threw so there is no such thing as automatic checked exception contract propagation and correct me if i am wrong, this is exactly what Java does, which you mentioned as an example earlier 2 AlaaMouch and DCzajkowski reacted with thumbs up emoji 9 nathggns, ottodimi, acomagu, Zelgadis87, parzhitsky, patrickJramos, zefir-git, TomasHubelbauer, and Enderchief reacted with thumbs down emoji All reactions * 2 reactions * 9 reactions Sorry, something went wrong. @nitzantomer Copy link Author nitzantomer commented Jan 11, 2017 @Aleksey-Bykov This: function mightThrow(): void { if (Math.random() > 0.5) { throw 'hey!'; } } function dontCare() { return mightThrow(); } Means that both mightThrow and dontCare are inferred to throws string, however: function dontCare() { try { return mightThrow(); } catch (e: string) { // do something } } Won't have a throw clause because the error was handled. This: function mightThrow(): void throws string | MyErrorType { ... } function dontCare() { try { return mightThrow(); } catch (e: string | MyErrorType) { if (typeof e === "string") { // do something } else { throw e } } } Will have throws MyErrorType. As for your keepAllValues example, I'm not sure what you mean, in your example: class MyClass { private values: number[] = []; keepAllValues(values: number[]) { for (let index = 0; index < values.length; index ++) { this.values.push(values[index]); mightThrow(); } } } MyClass.keepAllValues will be inferred as throws string because mightThrow might throw a string and that error was not handled. 22 patrickroberts, avik-so, Griffork, sakalys, DanielHreben, tommytroylin, uladkasach, zorji, liam-jones-lucout, nathggns, and 12 more reacted with thumbs up emoji All reactions * 22 reactions Sorry, something went wrong. 283 hidden items Load more... @owl-from-hogvarts Copy link owl-from-hogvarts commented Mar 19, 2023 function Foo(arr: unknown[]): void throws never { if (!arr.length) return; JSON.stringify(arr); arr.reduce(() => {}); } Compiler should fail here. Remember, that types in function signature denotes requirements for params, i.e. data, that should be supplied into function, and for output (return). By adding throws never clause we require function to never throw. I guess it can be compared to cpp's noexcept modifier. In the example, function may still throw something. But we definitely know, that call to arr.reduce will not throw. Compiler may fail to figure that out. In that case we may want to indicate that we know what we are doing. That is explicitly cast call to throws never. I don't know how this should look like, but: arr.reduce(() => {}) as Throws 3 zefir-git, acomagu, and luxaritas reacted with thumbs up emoji All reactions * 3 reactions Sorry, something went wrong. @Shakeskeyboarde Shakeskeyboarde mentioned this issue Mar 22, 2023 Require optional chaining for any type property access. #53438 Closed 5 tasks @royeradames Copy link royeradames commented Mar 26, 2023 Should be implemented 50 KodyJKing, from-nibly, Blockzilla101, worawut-w, woomiz, goncharov, EmJee1, dilame, cupoftea4, avidianity, and 40 more reacted with thumbs up emoji 5 curtgrimes, dead-claudia, HolgerJeromin, jgbpercy, and kachkaev reacted with thumbs down emoji 4 zerkms, juanigaray, AshleyRedman, and ericbf reacted with laugh emoji 2 SchroederSteffen and kvenn reacted with eyes emoji All reactions * 50 reactions * 5 reactions * 4 reactions * 2 reactions Sorry, something went wrong. @jsejcksn jsejcksn mentioned this issue Apr 10, 2023 BREAKING(semver): rewrite semver denoland/deno_std#3169 Closed @microsoft microsoft locked as resolved and limited conversation to collaborators Apr 19, 2023 @RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision and removed Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Apr 19, 2023 @RyanCavanaugh Copy link Member RyanCavanaugh commented Apr 19, 2023 * edited After reviewing all the comments here over the years and much discussion internally, we don't think that the JavaScript runtime or overall ecosystem provide a platform on which to build this feature in a way that would meet user expectations. Per popular request to either add the feature or close this issue for clarity, we're opting for the latter. As with Minification (#8), we're implementing a two-week cool-down period on further comments (ends 5/3). There are a few different facets that have been implied by the proposal and it's worth sort of breaking them apart individually: 1. The ability for a function to describe what kinds of exceptions it throws, with commensurate effects on catch clause variables, AKA typed exceptions 2. The ability to enforce that certain exceptions are explicitly handled (or declared as re-thrown), AKA checked exceptions Overall Observations on Exceptions in JavaScript We first need to examine how exceptions are used in JavaScript today to see how this fits into our goal of typing idiomatic JavaScript. Exception Introspection There are definitely some places in JavaScript where probing the thrown exception is useful, e.g. you might have code that is likely to throw a few kinds of known exceptions. TypeScript supports these well today with existing patterns: try { // ... } catch (e) { if (e instanceof TypeError) { console.log(e.message); } else if (typeof e === "string") { console.log(e.toUpperCase()) } else { throw e; } } Since there are usually extremely few static guarantees on what kind of values e might actually have, the existing dynamic type test patterns used in TypeScript are appropriate for writing safe code. More on that later. A proposed TC39 feature, pattern matching in catch clauses, would make these sorts of checks more ergonomic while at the same time providing useful runtime guarantees. If added to the language, TS would naturally support these. Examples in the future might look something like this: try { // ... } catch match ({ code: "E_NOENT" }) { // Syntax TBD, of course } catch match ({ code: "E_EXIST" }) { } Ecosystem Survey Looking at the landscape of JS libraries, the sort of rich inheritance hierarchies of various Error/Exception classes seen in languages like C# and Java are not widely adopted in the JavaScript ecosystem. For example, the lodash documentation is 200 pages, of which there is zero description of what kinds of exceptions are thrown, even though the source code reveals that a handful of functions are capable of throwing exceptions. The one apparent user-surfable throw in jQuery is not mentioned in the documentation. React mentions some of the exceptions it can throw, but not all of them, and only uses language like "throws an error", opting not to include specific information about what type of exception. An 850-page book on Material-UI never mentions exceptions, and only talks about throws from user code. There are no documented exceptions in xstate. The Svelte documentation, over the course of 100 pages, simply says "throws an error" in one occurrence. You cannot read the NodeJS documentation and accurately predict which properties will be present in a failing call like fs.open("doesnotexist", "r", err => { console.log (Object.keys(err)); }). In reality, passing invalid inputs to most JS libraries typically leads to exceptions only tangentially related to the error being made, e.g. passing a primitive where an object is expected in xstate produced uncaught TypeError: Cannot use 'in' operator to search for 'context' in 32. JS programmers are generally expected to realize they made a mistake earlier in the call stack and fix their own problems, rather than to look for very specific errors like you would get in C#. This situation doesn't seem likely to change anytime soon. Overall, there isn't a culture of strongly-typed exceptions in JS, and trying to apply that culture after the fact is unlikely to produce satisfactory results. But why is that culture absent in the first place? It has to do with language capabilities, in both directions. Language Capabilities This culture is a predictable consequence of the way JavaScript exceptions work. Without a first-class filtering mechanism to provide the ability to only catch certain exceptions in the first place, it doesn't make much sense to invest in formalizing error types that can't be usefully leveraged by developers. The other reason that strongly-typed exception hierarchies are rarely used is that these sorts of exceptions are not needed in the same way as they are in other languages. A key observation is that languages with strong cultures of exception throwing and exception catching have critical constraints which aren't present in JS: * Pervasive explicit and imperative resource management, wherein every function needs critical cleanup code to ensure correct long-run operation of the program (free, delete, closing native handles, etc.). Modern languages use constructs more like using, which is coming to JS, or ownership models like Rust. * The inability to return disparate values from a function * Lackluster support for first-class functions (especially in their formative years) Let's discuss the last two in further detail. Inability to return disparate values from a function In many older languages, functions might be overloaded, but those overloads were statically resolved at compile-time, and the result types of those calls had to be statically known. In other words, in C, there isn't the same notion of a function that returns an int | char* the way that you might talk about a number | string in JavaScript. Especially in languages with checked exceptions, this results in a very typical pattern: Functions which return a value representing the most common case (for example, a file handle) and throw an exception in the uncommon cases (for example, a FileNotFoundException). Effectively, exceptions (and checked exceptions doubly so) are a workaround for a lack of union return types in a language. They force a caller to reason about the non-golden-path results of an invocation the exact same way that a JS function returning Buffer | undefined does. Exceptions do allow a sort of transparent pass-through of these edge cases, but this capability is not widely leveraged in JS programs -- it's very uncommon to see programs written in a way that they meaningfully catch a specific exception from inner stack frames. You might do this once or twice, for example, to issue a retry on certain HTTP error codes, but this logic isn't pervasive throughout your code the way it is in Java. JavaScript doesn't have this problem of lacking union return types, and in fact has multiple good solutions. In addition to simply returning various values and requiring the caller to type-test them, we see other emergent solutions. First-class functions Another way to handle this situation is to pass a function value that receives two parameters: openSomeFile((err, fileHandle) => { /* ... */ }) This approach is widely adopted in the NodeJS API and is specifically reliant on how JS makes it much easier to write function values than languages like C, C++, C#, or Java did in their early incarnations. It's also convenient because, real talk, you can just pretend like err doesn't happen if you're writing code that doesn't need to be resilient. Or, use two separate callbacks: fetchSomething(err => { /* handle the error*/ }, data => { /* handle the data */ }); This monadic approach has gained wide adoption in libraries like fp-ts, and for good reason. Advantageously, it allows clear separation of "good" and "bad" paths, and forces upfront reasoning about what to do in failure cases. An interesting observation to make here is that it could be entirely idiomatic to specify multiple error parameters or callbacks in either pattern: openSomeFile((notFoundPath, accessError, diskRemoved, fileHandle) => { /* decide what to do */ }); // or fetchSomething( socketClosed => { /*do something */ }, diskFull => { /*do Something else */ }, data => { /*yay*/ } ); ... but it isn't. I think if you suggested this to a library developer, you'd get some very reasonable pushback: Adding new errors to fetchSomething shouldn't be a breaking API change, most callers do not care which kind of error happened, and we might not even know what kind of errors the underlying calls involved throw because that information isn't well-documented. That pushback applies equally to trying to document exception behavior in the type system. Generally speaking, JS code goes into exactly two paths: the happy case, or the "something went wrong" case, in which meaningful introspection as exactly what went wrong is quite rare. Avoidable and Unavoidable Exceptions For terminology's sake, generally we can think of two kinds of exceptions: * Avoidable: Those related to logical errors in the calling code, i.e. calling [].find(32). These kinds of exceptions "should" never occur in production code and can always be avoided by calling the function correctly. In other words, "you did it wrong". * Unavoidable: Those related to errors outside the programmer's control, i.e. a network socket being closed during transmission. These errors should be considered "always possible" and programmers should always be aware that they might happen. In other words, "something went wrong". Typed Exceptions Even setting aside the lack of exception typing in the wild, typed exceptions are difficult to describe in a way that provides value in the type system. A typical use case for typed exceptions looks like this try { someCode(); } catch (e) { // Primary suggestions on the table: // - Allow type annotations on 'e' if they // supertype what we think 'someCode' can throw // - Automatically type 'e' based on what // errors we think 'someCode' can throw } Current State of Support As a baseline, we need to look at how TypeScript handles these cases today. Consider some basic exception-inspecting code: try { // ... } catch (e) { if (e instanceof TypeError) { console.log(e.message); } else if (typeof e === "string") { console.log(e.toUpperCase()) } else { throw e; } } This code already works: * e.message is strongly-typed, and property access on e is correctly refined (even if e is any) * e.toUpperCase() is strongly-typed as well * More cases can be added, e.g. detecting e instanceof RangeError If we accept as broadly-true principles that... * Most JS code does not have documented exception behavior, nor strong versioning guarantees around it * Most JS code has at least some indirection, thus can always call code with undocumented exceptions * Safe handling of exceptions requires taking both of these into account then the code that works correctly today in TypeScript is the code that you should be writing in the first place. Setting that aside, let's look at some problems associated with trying to make this better. The Default Throw State of Unannotated Functions 100% of function declarations today don't have throws clauses. Given this, we'd have to make one of two assumptions: * An unannotated function will not throw any exception * An unannotated function might throw any exception If we assume all unannotated functions don't throw, the feature largely does not work until every type definition in the program has accurate throw clauses: // From a library that's annotated declare function doSomething1(): void throws NeatException; // From a library that's not annotated. // In reality, it can throw AwesomeException declare function doSomething2(): void; function fn() { try { doSomething1(); doSomething2(); } catch (e) { // e incorrectly claimed to be NeatException } } If we assume all unannotated functions do throw, the feature largely does not work until every type definition in the program has accurate throw clauses: // From a library that's annotated declare function doSomething1(): void throws NeatException; // From a library that's not annotated. // In reality, it does not throw declare function doSomething2(): void; function fn() { try { doSomething1(); doSomething2(); } catch (e) { // e claimed to be 'unknown' } } Assignability To keep exception information accurate, assignability would need to take into account throw information. For example: const justThrow: () => void = () => { throw new TypeError("don't call me yet"); } function foo(callback: () => void) { try { callback(); throwRangeError(); } catch (e) { // e: ? } } foo(justThrow); Depending on the meaning of unannotated functions, this program is either unsound (e marked as RangeError when it's actually TypeError), or rejected (the justThrow initializer is illegal). Neither option is particularly appealing. Having the program be accepted as unsound means the feature simply isn't working. That's not good, and to make matters worse, this would be the state of the world until every possible downstream call from the try body is accurately documented. Given the constraints of how well JS exceptions are documented in the first place, this is likely to never happen. Needing to reject this program is also unfortunate. The function justThrow is legal according to our current definitions, and the assignment doesn't seem to violate any particular rule. Creating additional hoops to jump through to make this program typecheck seems very difficult to justify. A potential fix would be to say that justThrow can throw any error marked as "avoidable" (in Java terms, using RuntimeException), thus making the assignment legal, but the problem is that existing JS programs don't make this distinction on the basis of the error object itself. It's more a property of the throw itself that is avoidable or unavoidable, information which is not inferrable from the way the function is written but rather is a part of the human-facing semantics of it. Getters / Setters Getters and setters in JavaScript can throw too, so the problem of non-annotation is also present in property declarations. A default of "does not throw" is obviously more palatable here, but leads to questions of how throw types would interact with assignability. For example, if we assume property get/sets don't throw, this program appears to have a type error because Array#length throws a RangeError if the assigned value is negative or too large: function clearArray(obj: { length: number }) throws never { obj.length = 0; } clearArray(someArray, n); But in reality this program is entirely fine, and it's not obvious what kind of type annotation you should have to write in order to have TypeScript accept it. Propagation of Exception Information We'd also have to be able to reason about a huge number of interacting criteria, and start demanding information from type definitions that people have never had to think about before. Let's consider some extremely simple code. try { fn(arg); } catch (e) { // e: ??? } Questions that need answering in order to make meaningful determinations about e: * What exceptions can fn throw assuming that arg matches its declared type? * What exceptions can fn throw if not? * Are there other unavoidable conditions fn might throw under? * If arg is a function, does fn invoke it? + If so, does it wrap that invocation in a try/catch ? o If so, does it conditionally re-throw some inclusionary or exclusionary subset of those exceptions? * If arg isn't a function, but has function-valued properties, does fn invoke any of those? + If so, which? Are those invocations wrapped in a try/catch ? etc. * Is any of this actually documented by the library author? For example, the two lines of code at the bottom of this example are the same in terms of syntax, but have different semantics: function justThrow() throws NeatError { throw new NeatError(); } const someArray = [0]; // Should propagate NeatError to its control flow someArray.forEach(justThrow); // Should not propagate NeatError to its control flow window.setTimeout(justThrow); Many of these questions require answers from the programmer. Every function declaration needs to change signature to represent this information. Instead of function callMe(f: () => void): void; You might need to write something like function callMe(f: () => void): void rethrows exceptions from fn except RangeError; or function callMe(f: () => void, g: () => void): void does not rethrow from f but does rethrow from g; or function callMe void }>(f: T): void rethrows exceptions from T["func"] if they are TypeError; or dozens of other variants that might exist. But to the last point, as we started with, there is not a strong culture of documentation or adoption of strong exception types in the JS world, nor describing the behavior of what code does when an exception occurs in the first place. This is all not even getting into problems like how to reason about exceptions that occur during future event loop ticks (e.g. Promise). Indeed, if JS had a culture of not describing input and output types at all, it'd be very difficult for TypeScript to have bootstrapped. Thankfully it's quite difficult to program without that sort of basic information, so input and output types are generally well-documented. But this isn't true for exceptions. Checked Exceptions A related feature request is the ability to have checked exceptions, as in Java or Swift. This feature require functions to either catch specific exceptions, or declare that they re-throw them. Certain exceptions are subject to this checking, and certain ones aren't. Beyond Java and Swift, though, no other mainstream programming language has adopted this feature. The common opinion among language designers, including ourselves, is that this is largely an anti-feature in most cases. Checked exceptions aren't seen in any of the widely used-and-liked languages today, with most new languages opting toward something closer to the Result pattern of Rust or a simpler unchecked exception model. Porting this feature to the JS ecosystem brings along a huge host of questions, namely around which errors would be subject to checking and which wouldn't. The ES spec itself defines over 400 places where an exception is thrown, and the spec clearly doesn't make a hard distinction between avoidable and unavoidable exceptions because it wasn't written with this concept in mind. The distinction is also fuzzy in some cases. For example, TypeError is thrown by JSON.stringify when encountering a circular data structure. In some sense, this is avoidable because many calls to JSON.stringify, by construction, cannot produce circularities. But other calls can. It's not really clear if you should have to try/catch a TypeError on every call here. Similarly, SyntaxError is thrown by JSON.parse if the input is invalid. This might be impossible in your application, or might not be. Erring on the conservative side, we might say that this exception is unavoidable since in at least some scenarios, you might be getting arbitrary data from the wire and trying to parse it. But SyntaxError is also the error thrown by the RegExp constructor on invalid regex syntax. Constructing a RegExp from non-hardcoded input is so vanishingly rare that it seems obnoxious to force a try/catch around new RegExp("[A-Z]") since that technically "can" throw an unavoidable exception, even though by inspection it clearly doesn't. Reconsideration Points What would change our evaluation here? Two primary things come to mind: * Widespread adoption and documentation of strong exception hierarchies in JS libraries in the wild * TC39 proposals to implement some sort of pattern-matching criteria to catch exceptions (arguably this would just work "by itself" the same way instanceof works today) TL;DR * Any feature here implies a huge amount of new information in .d.ts files that isn't documented in the first place * "Good" exception introspection (catch (e) { if (e instanceof X ) {) already works today * Anything more inferential than that is unlikely to be sound in practice 61 shonya3, lgharibashvili, michaele-blend, clintonb, wderezin, oscartbeaumont, quentin-sommer, kevinmmmcs, Quanyails, icehaunter, and 51 more reacted with thumbs up emoji 22 zefir-git, GiovanniCardamone, Guziq, BOTKooper, tjpalmer, pupudu, ArmorDarks, iHaPBoy, Melchyore, PhilippDehler, and 12 more reacted with thumbs down emoji 1 mjperrone reacted with hooray emoji 2 vorant94 and va3y reacted with confused emoji [?] 14 chriskrycho, Akatroj, quentin-sommer, acutmore, niieani, H4ad, toverux, maninak, cvpcasada, fklingler, and 4 more reacted with heart emoji 3 3imed-jaberi, BuZZ-dEE, and roninjin10 reacted with eyes emoji All reactions * 61 reactions * 22 reactions * 1 reaction * 2 reactions * [?] 14 reactions * 3 reactions Sorry, something went wrong. @RyanCavanaugh RyanCavanaugh closed this as not planned Won't fix, can't repro, duplicate, stale Apr 19, 2023 @microsoft microsoft unlocked this conversation May 5, 2023 @MartinJohns MartinJohns mentioned this issue May 8, 2023 Mark that method/function throws an error #54176 Closed 5 tasks @fatcerberus Copy link fatcerberus commented May 8, 2023 * edited In other words, in C, there isn't the same notion of a function that returns an int | char* the way that you might talk about a number | string in JavaScript. I would even go so far as to argue that TypeScript is mostly unique in having pervasive first-class untagged unions^1; the vast majority of statically typed languages, if they support sum types at all, support only tagged unions. Even Haskell. It only works because there are already tons of ways to differentiate values at runtime in JS. If you were to write e.g. union { int i; char* s; } in C you'd have no way to tell them apart. Footnotes 1. Which is perhaps unfortunate; the implications of untagged union and intersection types are very interesting from a type-theoretical perspective. But I digress. - 1 codingedgar reacted with thumbs up emoji All reactions * 1 reaction Sorry, something went wrong. @icehaunter icehaunter mentioned this issue Jun 29, 2023 feat(satellite) add support for shape subscriptions on satellite client electric-sql/electric#196 Merged @alexgleason alexgleason mentioned this issue Jun 29, 2023 Fix nip27.matchAll crash on invalid nip19 nbd-wtf/nostr-tools#239 Merged @kaleidawave Copy link kaleidawave commented Jul 9, 2023 I have a few insights so thought I would write some here. The first is what benefits having a type defined for the err variable in a catch would bring: * Being able to understand when statements can throw. Given getConfig() it would be nice to get feedback in the editor whether it throws a ParseError | InvalidConfig or a NetworkError which would help with what handling should do. Currently figuring that is left to reading source and reading documentation, which is what existing typing support aims to reduce. * type safety within the source, this is a good goal but given all the other loopholes TS has this doesn't complete or finish the puzzle. Inference of thrown values The big problem as noted is that this requires adding / changing pretty much all function type descriptors. Ones that aren't changed would have to assume some value. IMO to do this properly and today TS needs to able to recognise thrown types from functions. To do this TS would need to add an 'events system'. When synthesizing a function, it would need to record all throw events. Handing throw statements is trivial, the hard part is function calls (and as mentioned that getters) as they can throw from there invocation. TS would need to record values of parameters etc, it all gets quite complex. The hard parts that code from throw in an inferred events system. * Proxy, objects after Object.defineProperty, etc * Generators * Calling any-ish types * What internal functions (standard library, node, browser) throw * What internal functions access and call. e.g. customElements.define("...", class A {}, { get extends() { throw "Extends read!" } }) * non synchronous calls, e.g. the function argument to queueMicrotask This would be a big change to TS's direction, moving away from type annotations as the source of truth instead to code as truth... --------------------------------------------------------------------- Another part that I haven't seen mentioned, is not just registering throw, but also errors emitted from the JS engine itself (aka TypeError and ReferenceError). Undefined variables, getting properties on a null are cases. Currently they are a hard type error. Could that be relaxed, rather than being a error at the call site it would be an error that it is uncaught by a catch? e.g. could you read .a on { a: string } | { b: number }. --------------------------------------------------------------------- The checked exceptions is also interesting task. For some contexts like the top level, uncaught errors would be useful to be reported. Functions on the other hand there are uses cases for errors to bubble up. For the callback for setTimeout it would be good to make sure it doesn't throw / is handled, maybe it could take a PureFunction instead? --------------------------------------------------------------------- Finally, while I do like Rust's Result structure, I think JS is missing a lot of the infrastructure for the using { ok: T } | { err: E } pattern (notably match and ?). I can't see a increase in the result pattern coming soon as would require changing browser APIs and moving away from a huge part of the specification... All reactions Sorry, something went wrong. @zefir-git Copy link zefir-git commented Jul 9, 2023 * edited @kaleidawave in my view a function's throws type could be implemented the same way as its regular returns type. I do not see why events system would be needed. Just like a return statement, an uncaught throw statement terminates a function. TypeScript is already able to detect return types when calling methods (and getters too). Example: class A { public foo?: string; public get bar() { return this.foo ?? "was undefined"; } public baz() { return this.bar; } } const a = new A().baz(); // type detected as `string` https://tsplay.dev/weaLKw Therefore, I do not see how this would be much different: class A { public foo?: Error; public get bar() { throw this.foo ?? new TypeError("was undefined"); } public baz() { return this.bar; } } try { new A().baz(); } catch (e) { // e: Error | TypeError } Could be further simplified by adding optional throws to functions (just like specifying the return type can be optional). All reactions Sorry, something went wrong. @titouandk Copy link titouandk commented Jul 9, 2023 * edited This is a huge problem in JS/TS. Some libraries throws exceptions as a normal part of the flow - as a sentinel value if you wish. Developers of those libs expects you to catch those informative exceptions. The problem? You are not even informed of their existence. A concrete example: You would expect the following code (copying source into destination) to fail silently if the file already exists in destination - as the linux command cp -n source destination does: await vscode.workspace.fs.copy(source, destination, { overwrite: false }); But instead, in this case, this function will throw an exception to inform you that the file already exists at destination! Yes. Indeed. It will throw an exception to let you know that everything is fine! And you are not even warned of this possible behavior by the type system! This is hell JS devs do not deserve to get PTSD-like symptoms each time they call a function - worrying that it may explode to their face at any time, without warning. 20 stevenfukase, EHadoux, mnn, Nerixyz, kvenn, YolCruz, thelinuxlich, subframe7536, aslilac, galaxynova1999, and 10 more reacted with thumbs up emoji [?] 5 Profesor08, kvenn, lheasysoft, aslilac, and gastonmorixe reacted with heart emoji All reactions * 20 reactions * [?] 5 reactions Sorry, something went wrong. @thelinuxlich Copy link thelinuxlich commented Jul 11, 2023 throws should work solely for functions that were typed to use it, none more, none less. 7 from-nibly, aslilac, stevenfukase, Nabihabou, KashubaK, kvenn, and gastonmorixe reacted with thumbs up emoji All reactions * 7 reactions Sorry, something went wrong. @galaxynova1999 Copy link galaxynova1999 commented Jul 11, 2023 * edited why is this issue closed? Is there any definitive conclusion? 1 RadekRobot reacted with thumbs up emoji All reactions * 1 reaction Sorry, something went wrong. @ericbf Copy link Contributor ericbf commented Jul 11, 2023 Just scroll up. #13219 (comment) All reactions Sorry, something went wrong. @KashubaK Copy link KashubaK commented Aug 24, 2023 * edited I love the idea of checked exceptions. Personally I see value in this in regards to our team projects. If I implement function X that throws errors, and a junior developer wants to use it, they should handle the required errors. If TS caught them, I wouldn't have to rely on having to flag it down in code review (or if someone else was conducting the review, hoping the reviewer knew about the errors!) As noted, annotating function signatures sounds like it could be pretty nutty. I wonder if this information could be placed alongside throw statements with new safe/unsafe keywords, for example: async function doSomething(arg1: string, arg2: number) { if (arg1.length > 10) { throw safe new StringTooLongError(); } if (arg2 < 0) { throw safe new NumberLessThanZeroError(); } // `safe` meaning "You did it wrong" error, don't enforce try/catch // However if you think the developer absolutely should be required to try/catch... try { return await fetch('http://some-api.com/'); } catch (err) { // fetch can throw "TypeError" (i.e. 'Failed to fetch') // If the fetch types were updated, it could even force the developer to handle it. // Regardless, if the developer knows of an undocumented, unsafe error, they could make it unsafe by proxy: throw unsafe new RequestFailedError(); } // Non-annotated errors behave as-is: throw new Error('Whoops!') } In this case, the resulting type signature would be something like: function doSomething(arg1: string, arg2: number): Promise throws unsafe RequestFailedError | safe StringTooLongError | safe NumberLessThanZeroError; Example usage: await doSomething("hello", 5); // TS error: unsafe RequestFailedError exception must be caught try { await doSomething("hello", 5); } catch (err) { // TS error: unsafe RequestFailedError exception not narrowed in catch block // err: RequestFailedError | StringTooLongError | NumberLessThanZeroError // Problem: after handling all of the above, does err become never, or unknown? // Ideally it would become unknown, but current union behavior would reflect never. } In the case of an un-documented error, to avoid unintentional inference, they should be ignored completely. throw new UnknownError () should not be included in throws annotations. This ensures that if a developer upgrades their TS package, they don't get hit with 1,000+ type errors requiring a bunch of copy/paste try {} catch {}. Though perhaps a strictErrors option could be opted-out-of in tsconfig.json. I think this results in a much clearer experience. Developers opt into annotation, explicitly choosing which exceptions need to be handled and which ones don't. Or avoid it entirely if undesirable, however I imagine library consumers would argue otherwise. A counter argument to this may be "If we don't enforce annotation, we're stuck with the same problem that exists now with the lack of documentation." My response to this is, by providing rails (i.e. the "right way") then maintainers have to think less. I'm more likely to add one-word "unsafe" to my code, as opposed to maintaining the alternative in some sort of API documentation (that may/may not exist in the first place.) This leads into a point regarding one of the reconsideration points: Widespread adoption and documentation of strong exception hierarchies in JS libraries in the wild In my opinion, TypeScript is best positioned to champion this effort with this feature. I don't believe, without some sort of innovation, that this could be achieved. The idea here is incremental adoption, to encourage developers (and library maintainers) to handle errors more elegantly. Just because the culture isn't prevalent doesn't mean it can't be developed over time. One of the biggest complaints about JS is it's the "wild-west" where anything could go wrong, and this would be a good step in the right direction. To address the point of: Any feature here implies a huge amount of new information in .d.ts files that isn't documented in the first place I think that's one of the main reasons developers want this feature. If this were to be implemented, I'm sure we'd find many consumers asking maintainers to "annotate exceptions in X function". This working out-of-box for all existing TS projects is not feasible, however given time for adoption, the ecosystem could vastly improve. Constructing a RegExp from non-hardcoded input is so vanishingly rare that it seems obnoxious to force a try/catch around new RegExp("[A-Z]") since that technically "can" throw an unavoidable exception, even though by inspection it clearly doesn't. I wonder if there could be some sort of way to bypass this. // RegExp constructor is unsafe by default const regex = new RegExp("[A-Z]") as safe; This sounds scary, but various linter rules could be implemented to push developers away from using as safe if desired. Alternatively, a function could work around this if implementing as safe in TS is not feasible: function safe(fn: () => T throws unsafe unknown): T { try { return fn() } catch (error) { throw safe error; } } const regex = safe(() => new RegExp("[a-z]")); Which less ideal, but more readable than having to do try/catch manually everytime. --------------------------------------------------------------------- Forgive me if any of these suggestions are naive or lack understanding! This feature seems very ideal to me and I'd rather the conversation continue than otherwise. In closing, my two-cents would be: * Leave undocumented errors as-is * Encourage new code to document errors by developer demand * Allow developers to opt-out of checked exceptions via a strict option * Make it easy to document errors to mitigate required effort * Incremental adoption is better than none 7 kvenn, obedm503, zefir-git, yuhr, ivanhofer, acomagu, and EHadoux reacted with thumbs up emoji 1 kvenn reacted with hooray emoji [?] 2 kvenn and zefir-git reacted with heart emoji 2 kvenn and gastonmorixe reacted with rocket emoji All reactions * 7 reactions * 1 reaction * [?] 2 reactions * 2 reactions Sorry, something went wrong. @gastonmorixe Copy link gastonmorixe commented Aug 25, 2023 * edited +1 I think consensus seems to be we are not requesting a perfect solution, a good one would be a great first step. This is a key place TS can help to reduce bugs and improve developers' efficiency to al least warn them against some known possible errors. It shouldn't be that hard. I liked @KashubaK safe and unsafe idea as well. Here's the swift official doc about throwing functions https://docs.swift.org/swift-book/documentation/ the-swift-programming-language/errorhandling/ and a good example class VendingMachine { var inventory = [ "Candy Bar": Item(price: 12, count: 7), "Chips": Item(price: 10, count: 4), "Pretzels": Item(price: 7, count: 11) ] var coinsDeposited = 0 func vend(itemNamed name: String) throws { guard let item = inventory[name] else { throw VendingMachineError.invalidSelection } guard item.count > 0 else { throw VendingMachineError.outOfStock } guard item.price <= coinsDeposited else { throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited) } coinsDeposited -= item.price var newItem = item newItem.count -= 1 inventory[name] = newItem print("Dispensing \(name)") } } 3 owl-from-hogvarts, kvenn, and gastonmorixe reacted with thumbs up emoji 1 kvenn reacted with hooray emoji [?] 2 kvenn and gastonmorixe reacted with heart emoji All reactions * 3 reactions * 1 reaction * [?] 2 reactions Sorry, something went wrong. @KashubaK Copy link KashubaK commented Aug 25, 2023 * edited On the point of the TS team's desire for an existing adoption of some sort of exception handling standard, I wonder what that could look like. Perhaps some other CLI tool that looks at @throws JSDoc tags? There was some mention of this in #31329, however I do agree that it's out of scope for TS to type check based off JSDoc comments. If JSDoc and new TS syntax is out of the question for now, then the alternative is some sort of descriptor file that documents a file's exception handling. (To be clear, I'm not suggesting TS support something like this. I'm spitballing an idea for a different tool.) For example: src/VendingMachine.ts export class VendingMachine { inventory = { "Candy Bar": new Item({ price: 12, count: 7 }), "Chips": new Item({ price: 10, count: 4 }), "Pretzels": new Item({ price: 7, count: 11 }) } coinsDeposited = 0; vend(name: string) { const item = inventory[name]; if (!item) { throw new InvalidSelectionError(); } if (item.count === 0) { throw new OutOfStockError(); } if (item.price > this.coinsDeposited) { throw new InsufficientFundsError({ coinsNeeded: item.price - this.coinsDeposited }) } this.coinsDeposited -= item.price; item.count -= 1; console.log(`Dispensing ${name}`) } } Then an adjacent file that documents this: src/VendingMachine.errors.ts export default { VendingMachine: { vend: { safe: { // Could accept an object containing more documentation error: InvalidSelectionError, description: "Lorem ipsum sit dolor amet", }, unsafe: [ // If there are multiple errors, use an array OutOfStockError, // For ease of use, accept the error class without extra documentation { error: InsufficientFundsError, description: "coins deposited is less than the item price" } ] } } } Then have a CLI tool that introspects this information and inlines errors/warnings where applicable. I don't know how feasible this would be, or if there are improvements that could be made. However I think if we're passionate about this feature, we should try to address the TS team's concerns in one way or another. If some sort of solution could be built and there is sufficient developer demand for adoption then a TS implementation could be warranted in the future. 1 kvenn reacted with thumbs up emoji [?] 2 kvenn and gastonmorixe reacted with heart emoji All reactions * 1 reaction * [?] 2 reactions Sorry, something went wrong. @kvenn Copy link kvenn commented Aug 25, 2023 * edited @KashubaK, riffing off that same idea, I thought about creating an ESLint rule since that's already a widely adopted standard. It has some serious limitations, but maybe ones that someone who has built a rule before could weigh in on. The benefits would be that, for anyone using ESLint, it would be just one line they'd need to add to their config (or two, if we made multiple rules). I took a pass at a convo with ChatGPT which I'll share here in case it has some merits: https://chat.openai.com/share/ 8354ae88-a968-41c8-ab9a-eec8a23fa5ef The requirement I gave it: /** * @throws {@link TypeError} if the notification is not found */ function calleeFunction1() { } /** * This doc has text on top * @throws {@link TypeError} if the notification is not found */ function calleeFunction2() { } /** * @throws {@link TypeError} if the notification is not found * This doc has text on bottom */ function calleeFunction3() { } function callerFunction() { // This line should have a lint warning because it isn't wrapped in try / catch calleeFunction1() // So should this one calleeFunction2() // So should this one calleeFunction3() } The relevant es-lint rule it created module.exports = { rules: { "enforce-exception-handling": { create: function(context) { const sourceCode = context.getSourceCode(); let functionsThatThrow = new Set(); let scopeChain = []; function enterScope(node) { scopeChain.push(node); } function exitScope() { scopeChain.pop(); } function currentScope() { return scopeChain.map(node => sourceCode.getText(node)).join(' '); } return { Program: enterScope, FunctionDeclaration: enterScope, FunctionExpression: enterScope, ArrowFunctionExpression: enterScope, "Program:exit": exitScope, "FunctionDeclaration:exit": exitScope, "FunctionExpression:exit": exitScope, "ArrowFunctionExpression:exit": exitScope, FunctionDeclaration(node) { const throwsComment = node.leadingComments && node.leadingComments.find(comment => comment.value.includes("@throws")); if (throwsComment) { functionsThatThrow.add(currentScope() + ' ' + node.id.name); } }, CallExpression(node) { const functionId = currentScope() + ' ' + node.callee.name; if (functionsThatThrow.has(functionId)) { let parent = node; while (parent) { if (parent.type === "TryStatement") return; parent = parent.parent; } context.report({ node, message: "The function " + node.callee.name + " can throw an exception and needs to be called inside a try-catch block." }); } } }; } } } }; As a side note, I agree with everything you've said. This would be a MASSIVE level-up to TypeScript and I'm sure we could find a way to make this feature opt-in for those who value it. And I bet we'd all be surprised how quickly it's adopted (by large library authors). Function calls into libraries that throw undocumented errors (even though they explicitly call throw) has been a real struggle with choosing node as my server solution. [?] 2 tjpalmer and gastonmorixe reacted with heart emoji All reactions * [?] 2 reactions Sorry, something went wrong. @KashubaK Copy link KashubaK commented Aug 25, 2023 * edited @kvenn I wonder if that could also be implemented in a Language Service Plugin? As for actually emitting errors, we could adopt a strategy akin to how tsc-strict has a CLI that will actually throw them (for CI or other purposes) One reason I'm leaning away from relying on @throws is the lack of segregation between safe and unsafe errors [?] 2 tjpalmer and gastonmorixe reacted with heart emoji All reactions * [?] 2 reactions Sorry, something went wrong. @KashubaK Copy link KashubaK commented Aug 25, 2023 * edited I'm kind of liking the idea of a JSDoc linter rule solution, actually. Ideally it could: * Warn undocumented throw statements * Error on uncaught "unsafe" exceptions * Warn on uncaught "safe" exceptions In order to make this impactful a significant effort will be required. 1. Implement a JSDoc based linter rule (arguably the easiest step in this process.) 2. Advocate for popular libraries to document @throws in their code + This might look like "require future PRs to add @throws in new code" to start 3. Listen for push-back and address concerns 4. Submit PRs that adds this documentation to help get maintainers on board 5. Advocate for boilerplate generators (yarn create vite, create-react-app, etc.) to include our linter rule Perhaps even go over to DefinitelyTyped and elsewhere to add documentation for libraries that we personally use. I also wonder how the TS team would respond to incrementally updating the lib types to include @throws: https://github.com/microsoft/ TypeScript/tree/main/src/lib Considering the 1,400+ upvotes on this issue, if we had a solid plan we could make this work! [?] 5 tjpalmer, zefir-git, Profesor08, kvenn, and gastonmorixe reacted with heart emoji All reactions * [?] 5 reactions Sorry, something went wrong. @gastonmorixe Copy link gastonmorixe commented Aug 25, 2023 @RyanCavanaugh would it be possible to reopen this please? Thank you All reactions Sorry, something went wrong. @KashubaK Copy link KashubaK commented Aug 25, 2023 * edited I don't think there's any actionable items in regards to this specific issue at this time. I think the next step should revolve around implementing a solution that encourages error checking in hopes to create widespread adoption of error documentation. After that point I imagine this issue could be brought back up, though I am definitely curious about their thoughts on our recent comments. I'm going to try my hand at a linter rule over the weekend. If it goes well, I'll link the repo back here and we can move that conversation there. All reactions Sorry, something went wrong. @gastonmorixe Copy link gastonmorixe commented Aug 25, 2023 I think it's important enough to not be closed and encourage more discussion All reactions Sorry, something went wrong. @kvenn Copy link kvenn commented Aug 25, 2023 Agreed @KashubaK, I think the TypeScript team has made their opinion known. While I personally don't agree with the decision to deprioritize exception handling, I understand it. They don't think people will adopt it, and so it's on the community to prove otherwise. (That being said, I'd love if they reconsidered :)) Their reconsideration points made that clear: Reconsideration Points What would change our evaluation here? Two primary things come to mind: 1. Widespread adoption and documentation of strong exception hierarchies in JS libraries in the wild 2. TC39 proposals to implement some sort of pattern-matching criteria to catch exceptions (arguably this would just work "by itself" the same way instanceof works today) @KashubaK, your steps sound right to me. And your idea of integrating with DefinitelyTyped would be a massive step towards adoption. If you go with the ESLint approach, it looks like eslint-plugin-jsdoc already has the first bullet point (via requires-throw). And can be a good source of inspiration. As far as the spec of how exactly it should work 1. It might be valuable to draft an RFC that covers how it will work (kind of like how it's outlined in the md) - if you're interested in input. 2. It might be easier to forego having a "safe" throws in the first version (if you're looking to shave off scope). And have a stance on checked vs unchecked exceptions. + Swift enforces checked exceptions (you have to specify that you throw and you always have to catch or declare throw) + Kotlin has no checked exceptions (you can throw, but there's no enforcement you catch - like TypeScript) o They support @throws annotation for interop + Java has checked exceptions (with some special unchecked ones) Java seems closest to the hybrid of supporting both (safe and unsafe). But I'd personally advocate for matching swift (checked exceptions), but with the ability to opt into * None, warning, and error versions of enforcing you declare @throws * None, warning, and error versions of enforcing you catch something that declares @throws 1 zefir-git reacted with thumbs up emoji All reactions * 1 reaction Sorry, something went wrong. Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment Assignees No one assigned Labels Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript Projects None yet Milestone No milestone Development Successfully merging a pull request may close this issue. Bump TS to 4.5! microsoft/workspace-tools 99 participants @thelinuxlich @bluelovers @LinusU @nijikokun @be5invis @trusktr @jimisaacs @zerkms @bensaufley @bennycode @moshest @roll @factoidforrest @gastonmorixe @nathggns @bunchjesse @etler @grofit @zpdDG4gta8XKpMCd @thw0rted and others Footer (c) 2023 GitHub, Inc. Footer navigation * Terms * Privacy * Security * Status * Docs * Contact GitHub * Pricing * API * Training * Blog * About You can't perform that action at this time.