TypeScript: Suggestion: `throws` clause and typed catch clause

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

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 1904
  • Comments: 279 (25 by maintainers)

Most upvoted comments

how is a checked throw different from Tried<Result, Error>?

type Tried<Result, Error> = Success<Result> | Failure<Error>;
interface Success<Result> { kind: 'result', result: Result } 
interface Failure<Error> { kind: 'failure', error: Error }
function isSuccess(tried: Tried<Result, Error>): tried is Success<Result> {
   return tried.kind === 'result';
}
function mightFail(): Tried<number, string> {
}
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);
}

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

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 ?
      • 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<T extends { func: () => 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<T, E> 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

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

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?

There is a great read by @ahejlsberg on typed exceptions: https://www.artima.com/intv/handcuffs.html

I believe TypeScript is in a good position to avoid these issues. TypeScript is all about pragmatic solutions to real-world problems. In my experience, in big JavaScript and TypeScript codebases, error handling is one of the biggest problems - seeing which errors functions might throw and I might want to handle is incredibly hard. It is only possible through reading & writing good documentation (we all know how good we are at this /s) or looking at the implementation, and since in opposite to return values, exceptions just propagate through function calls automatically, it’s not enough to just check the directly called function, but to catch 'em all, you would have to check nested function calls too. This is a real-world problem.

Errors in JavaScript are actually very useful values. Many APIs in NodeJS throw detailed error objects, that have well-defined error codes and expose useful metadata. For example, errors from child_process.execFile() have properties like exitCode and stderr, errors from fs.readFile() have error codes like ENOENT (file not found) or EPERM (insufficient permissions). I know many libraries that do this too, e.g. the pg database driver gives you enough metadata on an error to know which exact column constraint caused an INSERT to fail.

You can see a worrying amount of brittle regex checks on error messages in codebases because people are not aware that errors have proper error codes and what they are.

If we could define in @types declarations and lib.d.ts what errors these functions can throw, TypeScript would have the power to help us with the structure of the error - what possible errors there can be, what the error code values are, what properties they have. This is not about typed exceptions and therefor completely avoids all the issues with typed exceptions. It is a pragmatic solution to a real-world problem (in opposite to telling people to use monadic error return values instead - the reality of JavaScript land looks different, functions throw errors).

The annotation can be completely optional (or made required with a compiler flag). If not specified in a declaration file, a function simply throws any (or unknown). A function can manually declare to never throw with throws never. If the implementation is available, a function throws a union of all the exception types of the functions it calls into and its own throw statements that are not inside a try block with a catch clause. If one of them throws any, the function throws any too - this is okay, at every function boundary the dev has the opportunity to correct it through an explicit annotation. And in many many cases, where a single well-known function is called and wrapped in a try/catch (e.g. reading a file and handling it not being found), TypeScript can then infer the type in the catch clause.

We don’t need Error subclassing for this nor instanceof - error types can be interfaces, that specify error codes with string literals, and TypeScript can discriminate the union on the error code, or use type guards.

Defining error types

interface ExecError extends Error {
  status: number
  stderr: Buffer
}
function execFileSync(cmd: string): Buffer throws ExecError;
interface NoEntityError extends Error { code: 'ENOENT' }
interface PermissionError extends Error { code: 'EPERM' }
function readFileSync(file: string): Buffer throws NoEntityError | PermissionError;
interface AbortError extends Error { name: 'AbortError' }

Preventing a runtime error due to type mismatch

try {
  execFileSync('git', ['status'])
} catch (err) { // ExecError
  // Compile error: Cannot call method match on type Buffer!
  // Need to call toString() on Buffer or pass encoding to exec
  err.stderr.match(/not a git repository/)
}

Catching errors with type switch

let content: Buffer
try {
  content = readFileSync(file)
} catch (err) { // NoEntityError | PermissionError
  switch (err.code) { // type-safe string comparison, autocompletion for codes, no typos
    case 'EPERM': retryElevated(); break; // trigger UAC prompt
    default: throw err; // don't want to deal with ENOENT ¯\_(ツ)_/¯
  }
}
try {
  const resp = await fetch(url, { signal })
  if (!resp.ok) {
    // inferred error type
    // look ma, no Error subclassing!
    throw Object.assign(new Error(resp.statusText), { name: 'ResponseError', response })
  }
  const data = await resp.json()
} catch (err) { // AbortError | Error & { name: 'ResponseError', response: Response } | SyntaxError
  switch (err.name)
    case 'AbortError': return; // Don't show AbortErrors
    default: displayError(err); return;
  }
}

Inferred exception type

// throws URIError if URI is invalid, did you notice?
// - doesn't matter, TypeScript did
function uriToPath(uri: string): string {
  return uri
    .replace(/^file:\/\//, '')
    .split('/')
    .map(decodeURIComponent) // map<R, E>(fn: (v: T, i: number) => R throws E): R[] throws E
    .join(path.sep)
}
interface HttpError extends Error { status: number }
// Type-safe alternative to express-style middleware request patching - just call it (TM)
// The default express error handler recognises the status property
function checkAuth(req: Request): User throws HttpError {
	const header = req.headers.get('Authorization')
    if (!header) {
        throw Object.assign(new Error('No Authorization header'), { status: 401 })
    }
    try {
 		return parseHeader(header)
    } catch (err) {
		throw Object.assign(new Error('Invalid Authorization header'), { status: 401 })
    }
}

Should be implemented

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<number, string> {
    }
    function mightFailToo(): Tried<number, string> {
         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

This issue is still tagged Awaiting More Feedback, what could we do to provide more feedback ? This is the only feature I envy the java language

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.

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

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

I love this suggested feature. Exception typing and inference can be one of the best things in the entire programming history. ❤️

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

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

I assume i will not mistake if say this is the most wanted feature in TypeScript

I have a suggestion here different from all the above: track throws at the statement level, to align with JS completion semantics without burdening the type system with a full concept of a completion.

  • Return values of both functions and function types may be annotated with throws T. If omitted, the default for function types and properties is throws never, and function expressions simply infer from their body.
  • For expression statements, their throws type is the union of the throws types of every expression within them.
    • Function expressions have a throws type of never
    • Variable accesses (outside the obvious with) have a throws type of never
    • All primitive literals (like strings, numbers, and booleans) except template literals have a throws type of never
    • Enum accesses (where valid) have a throws type of never
    • Call expressions have a throws type equal to the union of the throw result of the callee and the throw result of all their parameters.
    • RHS (read) literal property access expressions have a throws type equal to the union of the throw result of their host object and the throws type of the property getter itself. RHS dynamic property access RHS expressions have a throw result equal to the union of the throws types of all possible accesses that could be made and the throw result of the dynamic property access itself.
    • LHS (write) property access expressions work similarly, but check the setter instead of the getter. I additionally propose not adding throws TypeError to account for objects potentially being frozen later, as 1. the readonly modifier is a lot more idiomatic than Object.freeze in TS and 2. how often are people mucking around with descriptors in TS anyways?
    • await and return within async functions of course will need to inspect their inner promise to determine their throws value.
    • Other expressions just have a throws type that’s the union of whatever their parameters’ throws type are.
  • For throw statements (and possible future expressions), the throws is the union of the expression’s throws type and the type of the expression itself.
  • For try/catch, try/finally, and try/catch/finally statements:
    • The throws type of the try block must be assignable to the type of the catch binding, if a catch binding is present.
    • The throws type of the statement itself, if a catch block is present, is the union of the catch block’s throws type and the finally block’s throws type (if present).
    • The throws type of the statement itself, if no catch block is present, is the union of the try block’s throws type and the finally block’s throws type.
  • For other statements, return for the throws type the union of all first-level inner statements’ and expressions’ (for if/switch/etc.) throws types.
  • For the purpose of assignability testing to a variable or parameter, the throws type is ignored. throws assignability is only applied to a function’s designated throws type and the value of a catch binding.
  • An extra --strictCatch flag can be added to change the default unknown/any type for catch bindings to the inferred throws type for their corresponding try. This can simplify a lot of exception handling code in practice, especially with Node where basically all file read errors are passed as exceptions.
  • I’m actively not proposing a --inferThrowsNever flag as enforcing checked exceptions in specific areas can be as simple as specifying throws never in a main-like function or in whatever entry points of the library/application. (There’s a few places where I would’ve found an explicit throws ... very useful.)

No code, as I kinda hastily typed all that up (and also likewise there may be some consistency errors in this - please let me know if anything’s unclear or inconsistent), but what are your thoughts on this? It allows typed exceptions, opting into checked exceptions piecemeal, and enforcing certain callback contracts like “don’t throw” without enforcing checking all of them.

DL;DR;

  • The reject function of the promises should be typed
  • Any type thrown within a try block should be inferred using an union into the error argument of the catch
  • error.constructor should be properly typed using the real type and not only any to prevent missing a thrown error.

Okay, perhaps we should simply clarify the what are our needs and expectations:

Errors are usually handled in 3 ways in js

1. The node way

Using callbacks (which can actually be typed)

Example of usage:

import * as fs from 'fs';

fs.readFile('readme.txt', 'utf8',(error, data) => {
    if (error){
        console.error(error);
    }
    if (data){
        console.log(data)
    }
});

Where fs.d.ts gives us:

function readFile(path: PathLike | number, options: { encoding: string; flag?: string; } | string, callback: (err: NodeJS.ErrnoException | null, data: string) => void): void;

Therefore the error is typed like so

    interface ErrnoException extends Error {
        errno?: number;
        code?: string;
        path?: string;
        syscall?: string;
        stack?: string;
    }

2. The promise way

Where the promise either resolve or reject, while you can type the resolved value, you cannot type the rejection often called the reason.

Here is the signature of a promise’s constructor: Note the reason is typed any

    new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;

It could theoretically have been possible to type them as follow:

    new <T, U=any>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: U) => void) => void): Promise<T>;

In this manner, we could still theoretically make a 1-1 conversion between a node callback and a promise, keeping all the typing along this process. For instance:

const fs = require('fs')
const util = require('util')

const readFilePromise = util.promisify(fs.readFile); // (path: PathLike | number, options: { encoding: string; flag?: string; } | string) => Promise<data: string, NodeJS.ErrnoException>;

3. The “try and catch” way

try {
    throw new Error();
}
catch (error: unknown) { //typing the error is possible since ts 4.0
    if (error instanceof Error) {
        error.message;
    }
}

Despite we are throwing an error “Error” 2 lines before the catch bloc, TS is unable to type the error (value) as Error (type).

Is it not the case neither using promises within an async function (there is no magic “yet”). Using our promisified node callback:

async function Example() {
    try {
        const data: string = await readFilePromise('readme.txt', 'utf-8');
        console.log(data)
    }
    catch (error) { // it can only be NodeJS.ErrnoException, we can type is ourself… but nothing prevents us to make a mistake
        console.error(error.message);
    }
}

There is no missing information for typescript to be able to predict what type of error could be thrown within a try scope. We should however consider internal, native functions might raise errors which are not within the source code, however if every time a “throw” keyword is in the source, TS should gather the type and suggest it as a possible type for the error. Those types would of course be scoped by the try block.

This is only the first step and there will still be room for improvement, such as a strict mode forcing TS to work like in Java i.e. to force the user to use a risky method (a method which can throw something) within a try block. And if coder does not want to do so, then it would explicitly mark the function as function example throw ErrorType { ... } to escalate the responsibility of handling the errors.

Last but not least: prevent missing an error

Anything can be thrown, not only an Error or even an instance of an object. Meaning the following is valid

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a number or an object of type Error
    if (typeof error === "number") {
        alert("silly number")
    }

    if (error instanceof Error) {
        alert("error")
    }
}

To know that the error could be of type number | Error would be incredibly helpful. However to prevent forgetting to handle a possible type of error it is not really the best idea to use separate if / else blocs without a strict set of possible outcomes. A switch case would however do this much better as we can be warned if we forgot to match a specific case (which one would fallback to the default clause). We cannot (unless we do something hackish) switch case an object instance type, and even if we could, we can throw anything (not only an object but also a boolean, a string and a number which have no “instance”). However, we can use the instance’s constructor to find out which type it is. We can now rewrite the code above as follow:

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a Number or an object of type `Error`
    switch (error.constructor){
        case Number: alert("silly number"); break;
        case Error: alert("error"); break;
    }
}

Horray… the only remaining problem is that TS does not type the error.constructor and therefore there is no way to narrow the switch case (yet?), if it would do so, we would have a safe typed error language for js.

Please comment if you need more feedback

Typescript has never guaranteed true type safety and likely will never be able too, but that’s not the point. Typescript is primarily a tool that helps us to write better more reasonable code. Claiming that there in no benefit to typed exceptions unless all functions that throw are annotated immediately and all at once is utter nonsense. One of Typescript’s philosophies is that you can incrementally adopt it in your code. So why would exception typing be any different? The sooner Typescript adds support for typed exceptions the sooner we will start seeing the benefits. Knowing about some exceptions allows us to handle those cases and thus reduce a class of bug/runtime errors. As we see the benefits we will see that third party libraries will start updating their types as well.

See the following for a summary of what I see being asked/talked about in this issue. https://github.com/microsoft/TypeScript/issues/52145

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

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.

Pinging @DanielRosenwasser and @RyanCavanaugh since they added the tags all those years ago: What’s the procedure? Is there anything missing? Is this finally enough feedback? Would it help to move this forward if I or someone else wrote a proper design proposal from the comments in this issue? Or is there any other ACTIONABLE step to take? This issue is blocking API features on my side…

Realizing that this proposal is older than both of my kids, I legitimately would like to know how to get it over the finish line.

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

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.

The suggestion is still “awaiting more feedback”, and has been inactive for months. That’s sad, I’d love to see this coming in typescript!

Hi, looks like my RFC is an apparent duplicate of this. Thought I’d post the thoughts from my issue here

That’s neat and well written, and I agree with most of it.

I don’t like the use of /, I prefer the explicit keyword throws for readability, but that’s just personal preference. We already use keywords like implements and extends so I don’t feel this is too out of line.

(One pedantic quibble being that if (!arr[0]) does not detect an empty array, it only checks if the first element is falsy… an empty array being one possible case)

@be5invis

function square(x: number): number &! never { // This function is pure
  return x * x
}
...

I’m just giving my two cents here but the &! looks incredibly ugly to me. I think people that are new to typescript would be kind of throw off by this symbol, it’s really uninviting. A simple throws is more explicit, intuitive and simple, in my opinion.

Other than that, +1 for typed exceptions. 👍

@dilame as of time of writing, it’s the 5th most commented and the 3rd with the most thumb ups. So probably not the most wanted, but it’s close enough.

EDIT: Although it has the most thumb ups out of the ones with most comments, so maybe we could say it’s the most active out of the most wanted.

Using some short/single character separator instead of throws, which is commonly used in other languages, but IMO doesn’t fit the short function declaration style of TypeScript

Thumbed up in general, but thumbed down for the usage of /. throws is more readable and introduces less of a barrier for new comers.

When I built my first Java app (coming from a more c# background), the way it enforced handling throwable functions was at first annoying - after propagating handling throughout the app, my final result was a very production-stable application on first try (ikr!). This saved tons of time and headaches overall and I very quickly appreciated that 😄 and would even go so far as to say that’s the best feature in Java. Now I work in a Typescript stack, and this feature would be very much appreciated. My suggestion to add to this: make it have differing levels of strictness: you can have “strict” mode which would operate similar to Java where it bubbles up throughout the app, or you could have a relaxed mode, where you are not required to mark a function “throws”, and thus, could avoid having to propagate try catches everywhere.

Why does this feature still “not enough feedback”? It’s so useful when invoking browser’s API like indexedDB, localstorage. It’s caused many failure in complex scenario but developer can’t aware in programing.

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

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<Response> 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<T>(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

To add my 5 cents: I see exceptions similar to a function returning null: It’s a thing you normally don’t expect to happen. But if it happens a run-time error occurs, which is bad. Now, TS added “non-nullable types” to remind you handling null. I see adding throws as taking these efforts one step further, by reminding you to handle exceptions as well. That’s why I think that this feature is definitely needed.

If you look at Go, which returns errors instead of throwing them, you can see even clearer that both concepts aren’t that different. Also, it helps you understand some APIs more deeply. Maybe you don’t know that some functions can throw and you notice it much later in production (e.g. JSON.parse, who knows how many more there are?).

@obedm503 This is actually a philosophy of TS: by default the compiler doesn’t complain about anything. You can enable options to treat certain things as an error or enable strict mode to enable all options at once. So, this should be a given.

Hi, looks like my RFC is an apparent duplicate of this. Thought I’d post the thoughts from my issue here:


📃 Motivation

TypeScript’s purpose has always been to “identify constructs that are likely to be errors”. This objective isn’t only seen as the number 1 TypeScript design goal, but also on the website, where it is advertised to “catch errors early”. And while its core type system has made great leaps forward striving to make this a reality, there is a common JavaScript programming pitfall that still causes unintended or unexpected production accidents at runtime to this day: (inadequate) error handling. Consider this code:

function first<T>(arr: T[]): T {
	if (!arr[0]) throw new RangeError("Array is empty");
	return arr[0];
}

// ... somewhere down the road ...

function main() {
	first([]); // Oops!
}
main(); // Uncaught RangeError: Array is empty

The code above leverages JavaScript’s built-in error handling system to throw an error if the array passed to the first() function is empty. However, (up until now) TypeScript won’t warn us about this when we invoke the function without proper error handling. One missing try-catch block, one missing .catch() handler can take down the entire application, causing a hard-to-debug crash somewhere in the stack. In certain cases, this could’ve been handled up-front with proper typings. For example, for the code snippet above, one could use a special array type that requires a minimum length of 1 array element (anecdotally, this is currently not possible to enforce for arbitrary-length arrays with TypeScript). One could’ve also opted for a different error handling pattern, returning undefined using TypeScript’s conditional type support, or returning a { data, error } object that can be destructured by the caller. In many cases, however, this is not viable, and introducing workarounds just piles on more overhead, anti-patterns and error surface on top of JavaScript’s well-functioning native capabilities. There are countless libraries that expose functions which might throw for one reason or another, and unless it can be explicitly ruled out using type heuristics only, the developer is required to dig through (sometimes terrible) documentation or the raw source code itself to figure out if (and what!) error handling is required for a certain function call. Let’s be honest - most of the time, this mind-numbing research is often left out for far too long, until it hits hard with unhandled errors in production. In an effort to advance TypeScript’s stated goal of eliminating ever-so-subtle JavaScript issues before they happen, this RFC proposes a new function declaration syntax to fix just that - no runtime overhead attached.

⭐ Suggestion

function first<T>(arr: T[]): T / RangeError  {
	if (!arr[0]) throw new RangeError("Array is empty");
	return arr[0];
}

That’s it. Using a / (forward slash) literal to separate the preceding function declaration from the thrown type (RangeError), TypeScript can now help the developer prevent a fatal bug:

function main() {
	first([]); // error TS????: Unhandled RangeError: Array is empty
}
main();

Of course, we might intentionally want the unhandled error to bubble up. Using the proposed extended function declaration syntax, this can be expressed intuitively:

function main() / RangeError {
	first([]);
}
main(); // error TS????: Unhandled RangeError: Array is empty

We can now handle the error at the top-level. The / (forward slash) character is reserved to define regular expressions, however I have not found a syntactically valid case where it can be used in a TypeScript function declaration. The proposed change has a few implications:

  • All existing lib/native type definitions will have to be extended with the error types they throw, if any
  • The error parameter in catch blocks can now be strictly typed using the gathered type information
  • Existing function definitions (without explicit error type declarations) imply no behavioral changes (i.e. they are expected to not throw)
  • Adding a new strict flag to enforce strict handling of errors

In contrast to this issue, I would suggest:

  • Using some short/single character separator instead of throws, which is commonly used in other languages, but IMO doesn’t fit the short function declaration style of TypeScript
  • Encouraging catching of unhandled errors using TypeScripts compilation-time checking and editor integration
  • Introducing a strict flag to enforce proper error handling

Is anyone interested in making the changes in a PR for me? 😄

I understand, this task is about types and propagating information so user can handle it. It is about assisting user with handling errors. I believe that propagating error type and infering it is very similar to return values - every function has return type and (possibly) throw type. Problem is with functions, that do not specify throw type (3rd party). Basicaly those functions throws any or unknown (we dont really know what they can throw) and these types widens … so MyError | any => any and MyError | unknown => unknown. And we loose all information. We can be never sure about all error types that can be thrown - except very basic functions. So when functions throws CustomError it really means it can throw unknown | CustomError

function a() { // inferred: number throws unknown
    return call3rdParty(); // we dont know type of Error
}

function b() { // inferred: string throws unknown | LuckError
    if(Math.random() > .5) { // can throw unknown
        return 'Success';
    } else {
        throw new LuckError('No luck this time');
    }
}

function c() { // inferred: number | string throws unknown | LuckError
    if(Math.random() > .5) {
        a();
    } else {
        b();
    };
}

So its possible to say, that in every catch clausule, we have union of errors and unknown: unknown | ErrorA | ErrorB .... Maybe its sufficient enough to propagate/infer existing types without uknown, but inside catch assume that its union of those types and unknown. Of course, union with unknown is always unknown, so we would need special unknown handling in catch or introduce new type, so we dont loose all information - for autocomplete especially.

Presenting possible error types to user just makes easier to handle those errors in code:

try {
    c();
} catch(e) { // e is unknown | LuckError <--- this is the part we care about

    // this is the part we DONT care about
    if(e instanceof LuckError) { // or and other condition
        // handle specific type
    } else { 
        // handle unknown
    }
}

Assisting user with possible errors solves everyday issues and improves code quality in long run. If we know, that JSON.parse can throw, IDE can underline/mark it and help even non experienced users handle these situations.

I think this is one of THE missing things in typescript type system. Its purely optional, doesnt change emitted code, helps working with libraries and native functions.

Also knowing what errors might be thrown could allow editors to “autocomplete” catch clause with if conditions to handle all errors.

I mean, really even small application deserves proper error handling - the worst thing now is, that users don’t know when something might go wrong.

For me, this is TOP missing feature now.

Hello, I just spent a little of time trying to find a workaround with what we do currently have in TS. As there is no way to get the type of the thrown errors within a function scope (nor to get the type of the current method by the way) I figured out on how we could however explicitly set the expected errors within the method itself. We could later, retrieve those type and at least know what could be thrown within the method.

Here is my POC

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Set an extra type to any other type
type extraTyped<T, E> = T & extraType<E>

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<T, E extends Error> = extraTyped<T,E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;
const getTypedError = function<T extends basicFunction> (error, throwableMethod:T) {
    return error as getExtraType<ReturnType<T>>;
};

/***********************************************
 ** An example of usage
 **********************************************/

class CustomError extends Error {

}

// Here is my unreliable method which can crash throwing Error or CustomError.
// The returned type is simply our custom type with what we expect as the first argument and the
// possible thrown errors types as the second (in our case a type union of Error and CustomError)
function unreliableNumberGenerator(): throwable<number, Error | CustomError> {

    if (Math.random() > 0.5) {
        return 42;
    }

    if (Math.random() > 0.5) {
        new Error('No luck');
    }

    throw new CustomError('Really no luck')
}

// Usage
try {
    let myNumber = unreliableNumberGenerator();
    myNumber + 23;
}

// We cannot type error (see TS1196)
catch (error) {
    // Therefore we redeclare a typed value here and we must tell the method which could have crashed
    const typedError = getTypedError(error, unreliableNumberGenerator);

    // 2 possible usages:
    // Using if - else clauses
    if (typedError instanceof CustomError) {
        
    }

    if (typedError instanceof Error) {

    }

    // Or using a switch case on the constructor:
    // Note: it would have been really cool if TS did understood the typedError.constructor is narrowed by the types Error | CustomError
    switch (typedError.constructor) {
        case Error: ;
        case CustomError: ;
    }
    
}

// For now it is half a solution as the switch case is not narrowing anything. This would have been 
// possible if the typedError would have been a string union however it would not be reliable to rely
// on typedError.constructor.name (considering I would have find a way to convert the type union to a string union)

more aware of the different errors that might be thrown

there is no point of doing so unless they can deal with them, the current proposal isn’t viable because of the cases i listed

So you propose that all errors must be handled like it is with java?

yes, this is what it means having checked exceptions

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

I also think that the noexcept specifier (#36075) is the simplest and best solution right now, as for most programers, throwing exceptions consider an anti-pattern.

Here are some cool features:

  1. Verifying functions will not throw an error:
noexcept function foo(): void {
  throw new Error('Some exception'); // <- compile error!
}
  1. Warning about redundant try..catch blocks:
try { // <- warning!
  foo();
} catch (e) {}
  1. Resolve always Promises:
interface ResolvedPromise<T> extends Promise<T> {
  // catch listener will never be called
  catch(onrejected?: noexcept (reason: never) => never): ResolvedPromise<T>; 

  // same apply for `.then` rejected listener,
  // resolve listener should be with `noexpect`
}

I was actually surprised that this functionality wasn’t already inside of TypeScript. One of the first things I went to declare. It would be okay if the compiler does or doesn’t enforce it, as long as you can get the information that there will be a thrown error, and what type of errors will be thrown.

Even if we don’t get the &! and just get the Throws<T> that would be 👍

@aleksey-bykov What you’re describing exists in all languages that support exceptions.
No one said that catching an exception will fix the problem, but it will let you handle it gracefully.

Knowing which errors can be thrown when invoking a function will help the developers to separate between the errors that they can handle and those which they can’t.

Right now developers might not know that using JSON.parse might throw an error, but if it was part of the lib.d.ts and the IDE would let him know (for example) then maybe he’ll choose to handle this case.

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<number, string>;
declare function doThat(): Tried<number, string>;
function withBothTried<T, E, R>(one: Tried<T, E>, another: Tried<T, E>, haveBoth: (one: T, another: T) => R): Tried<T, R> {
    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<number, string> {
    return withBothTried(
        doThis(),
        doThat(),
        add
    );
}

Completely agree. Is there a good reason not to merge this?

This is not a pull request.

This is a hack and by no means a solution, but it’s possible to annotate the thrown error as a return value by using a simple union. Since it’s a union, the function does not have to actually return the error.

Of course, the types are not enforced in any way - but it may be a step ahead compared to @throws tsdoc tag, as it’s possible to get the type of errors that are thrown (if we did not make a mistake in the annotation).

Playground

type Throws<T> = { throws: T };

type GetThrow<T> = T extends Throws<infer R> ? R : never;
type GetResult<T> = T extends Throws<unknown> ? never : T;

type GetFunctionThrow<T extends (...args: any[]) => any> = GetThrow<ReturnType<T>>;

// This type is needed to eliminate the Throws type from the union so the return
// value can be used properly
type GetFunctionResult<T extends (...args: any[]) => any> = GetResult<ReturnType<T>>;

type CalcError = {
    code: "calc_error"
    message: string
}

type DivisionError = {
    code: 'division_error'
    message: string
}

function divide(a: number, b: number): number | Throws<CalcError | DivisionError> {
    if (b === 0) {
        throw { code: "division_error", message: "Cannot divide by 0" } as DivisionError; 
    }

    return a / b;
}

function calc(a: number, b: number, operation: 'divide'): number | Throws<CalcError | DivisionError> {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw { code: "calc_error", message: "Required 2 number arguments" } as CalcError;
    }

    if (operation !== 'divide') {
        throw { code: "calc_error", message: "Unsupported operation" } as CalcError;
    }

    return divide(a,b);
}

let x: number;

try {
    x = calc(5 ,6, 'divide') as GetFunctionResult<typeof calc>;
} catch (e: unknown) { // Catch-clause annotation must be any or unknown
    const err = e as GetFunctionThrow<typeof calc>; // CalcError | DivisionError
}

Edit: I updated the example to eliminate Throws from function type, so the result can be used.

noexcept is the same as throws never.

@moshest

as for most programers, throwing exceptions consider an anti-pattern.

I guess I’m not part of that “most programmers” group. The noexcept is cool and all but it’s not what this issue is about.

If the tools are there, I’ll use them. throw, try/catch and reject are there, so I’ll use them.

It’d just be nice to get them properly typed in typescript.

function* range(min: number, max: number): Iterable<number> throws TypeError, RangeError {

I know I am a little late to this game on this, but the below seems a little more Typescripty syntax rather than comma.

Iterable<number> throws TypeError | RangeError

But I am still good with comma… I just wish we had this in the language. The main one is I would like it on JSON.parse() because a lot of my co-workers seem to forget that JSON.parse can thrown an error and it would save a lot of back-and-forth with pull requests.

@aleksey-bykov

you can’t handle a problem happened 20 layers below gracefully, because the internal state is corrupt in 19 layers and you can’t go there because the state is private

No you cannot fix the internal state, but could certainly fix the local state, and that’s exactly the point of handling it here and not deeper in the stack.

If your argument is that there is no way to be certain what state some shared mutable values are in when handling the exception, then it’s an argument against imperative programming, and not confined to this proposal.

@aleksey-bykov So you propose that all errors must be handled like it is with java?
I’m not a fan of that (even though I come from java and still loving it) because js/ts are way more dynamic and their users are accustomed to that.
It can be a flag that makes you deal with errors if you include it when compiling (like strictNullChecks).

My suggestion isn’t here to solve unhandled exceptions, the code you posted will break now without this feature implemented, and it would break in js as well.
My suggestion just let you as a developer be more aware of the different errors that might be thrown, it’s still up to you if to handle them or ignore them.

As for the division by 0 issue, it doesn’t result in an error:

console.log(1 / 0) // Infinity
console.log(1 / "hey!") // NaN

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 😃

We are also recently stumbled about this problem. Since there is no reasonable way to overcome this we already thought about switching to functional programming approaches as the way to describe an API call always also describes the error case. Switching a complete legacy code base to FP is hell.

Adding syntactical sugar with the possibillity of adding throws <MyException> would be a feature we would desperately emrace. So please, give an update on this since this feature request is nearly six years old or shortly explain why not to add this to any upcoming TS versions.

I think there’s been some confusion for different needs expressed in this single suggestion, and I think something like unhandled errors needs to be discussed in seperate issue, preferably after this suggestion get resolved.

For now we should focus on minmum functionality like infering error types from functions and catch clause. Additional syntaxes like catch-per-type doesn’t need to be implemented with error type inference, and will slow down the process towards actually implementing this into TypeScript.

I personally really want to see this happen, and I’m quite concerned too many feature requests might slow down the adoption of error type inference at all.

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

I’m following this issue because I was hoping that it could be resolved in a way that addresses both try/catch and async function rejection. The inconsistency between the two is pretty annoying – in strict mode, TS now types catch block arguments as unknown but Promise rejection (.catch signature) is still any. Hinting at what type is expected sounds great, but I at least want those two to be internally consistent.

Another bump, what’s blocking implementation? This is insanely helpful.

We have a use case where we throw a specific error when an API call returs non-200 code:

interface HttpError extends Error {
  response: Response
}

try {
  loadData()
} catch (error: Error | ResponseError) {
  if (error.response) {
    checkStatusCodeAndDoSomethingElse()
  } else {
    doGenericErrorHandling()
  }
}

Not being able to type the catch block ends up in developers forgetting that 2 possible types of errors can be thrown, and they need to handle both.

@ConnorSinnott

It’s not different from other languages that use the throw clause, i.e. java. But I think that in most cases you won’t really have to deal with a lot of types. In the real world you’ll usually deal with only string, Error (and subclasses) and different objects ({}). And in most cases you’ll just be able to use a parent class which captures more than one type:

type MyBaseError = {
    message: string;
};

type CodedError = MyBaseError & {
    code: number;
}

function fn1() throws MyBaseError {
    if (something) {
        throw { message: "something went wrong" };
    }
}

function fn2() throw MyBaseError {
    if (something) {
        throw { code: 2, message: "something went wrong" };
    }

    fn1();
}

As for when implicitly use the throw clause vs. having the compiler infer it, I think it’s just like declaring types in typescript, in a lot of cases you don’t have to, but you can do it to better document your code so that whoever reads it later can better understand it.

@RyanCavanaugh, there’s been a bunch of feedback since the “Awaiting More Feedback” label was added. Can any typescript team members weigh in?

I am getting what you’re saying, but I don’t agree with it. You’re right, that is basically what I’m saying. If I used a 3rd party library that throws an error I can choose to deal with it or ignore it and let the user of my code handle it. There are many reasons to do so, for example the lib that I’m writing is UI-agnostic, so I can’t inform the user that something is wrong, but who ever uses my lib can handle the errors that are thrown when using my lib and handle them by interacting with the user.

If a library is left with a corrupted state when it throws, then it probably needs to document it.
If I then use such a library and as a result in an error in it my state becomes corrupted then I need to document it.

Bottom line: This suggestion comes to offer more information about thrown errors. It shouldn’t enforce developers to do things differently, just make it easier on them to deal with the errors if they choose to.

@aleksey-bykov I don’t see why any of the cases you listed render this proposal as inviable.

There’s no problem with handling an error that was thrown way down the invocation chain, even if I’m using a function that was inferred of throwing DivisionByZero (regardless of where it was thrown), I can choose to handle it.
I can try to re-try it with different arguments, I can show the user a message that something went wrong, I can log this problem so that I can later change my code to handle it (if it happens often).

Again, this proposal doesn’t change anything in runtime, so everything that worked will continue to work as before.
The only difference is that I will have more information about the errors that might be thrown.

As for your keepAllValues example, I’m not sure what you mean

I meant the exceptions coming unhandled from mightThrow interrupt keepAllValues and make it finish in a middle of what it was doing leaving its state corrupt. It is a problem. What you suggest is to close your eyes on this problem and pretend it’s not serious. What I suggest is to address this problem by requiring that all checked exceptions are immediately handled and explicitly rethrown. This way there is no way to get the state corrupt unintentionally. And although it can still be corrupt if you choose so, it would require some deliberate coding.

Think about it, there are 2 ways we can go about exceptions:

  • unhandle them, which leads to a crash, if the crash is what you want then we are fine here
  • if you don’t want a crash then you need some guidance as to what sort of exception you need to be looking for, and this is where your proposal comes in: checked exeptions - all explicitly listed, so you can handle them all and don’t miss anything

now if we decided to go with the checked exceptions which are properly handled and prevent a crash we need rule out a situation when we handle an exception coming from several layers deep of where you are catching it:

export function calculateFormula(input) {
    return calculateSubFormula(input);
}
export function calculateSubFormula(input) {
   return calculateSubSubFormula(input);
}
export function calculateSubSubFormula(input): number throws DivisionByZero  {
   return 1/input;
}

try {
   calculateFormula(0);
} catch (e: DivisionByZero) {
   // it doesn't make sense to expose DivisionByZero from under several layers of calculations
   // to the top level where nothing we can do or even know what to do about it
   // basically we cannot recover from it, because it happened outside of our immediate reach that we can control
}

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

Another thing, should we also include an equivalent of ReturnType but for errors instead?

Something like:

type FunctionThatThrowsSomeError = () => string throws SomeError;

type FunctionReturnType = ReturnType<FunctionThatThrowsSomeError>; // string
type FunctionError = ThowsType<FunctionThatThrowsSomeError>; // SomeError

Maybe this has already been mentioned before though, I’m a bit fearful now that we might be losing information in such a long thread.

Hey, any movements here? For me, most important is to know what can throw ( as many mentioned before ). So I would be happy to annotate “left” and “right” return let’s say in terms of Either.

function division(x: number, y: number): Throwable<Error, number> {
   if (y === 0) {
     throw new Error('Ops division by zero'); // compiler checks if we throw only Error
   }
   return x/y; // compile checks number
}

// using
const a: number = division(1,2); // a is number that is fine, compiler checks only right argument of Throwable

I would be ok to allow for assign for the right thingy from Throwable<left, right>, but would be great if TS would at least tell me if function can throw and what can throw.

BTW. the original proposition syntax :returnType throws throwType is really great

It could be an option as part of typescrypt’s strict mode that a function must either catch the error or explicitly declare it throws and pass it along

Has there been any more thought into this discussion? I would love to see a throws clause in typescript.

you can disagree, it’s fine, let’s just not call them checked exceptions please, because the way you put it isn’t what checked exceptions are

let’s call them listed or revealed exceptions, because all you care is to make developers aware of them

throws should work solely for functions that were typed to use it, none more, none less.

Any news?

It might be worth looking at https://hegel.js.org/ which is a static type checker for JS that tracks what exceptions are thrown by functions and whether they’re caught or not.

@thw0rted @TomMettam

I feel like

type MyCallback = (x: number)
  => void
  throw Error;

is a good example of valid code which will change its meaning after an update using the keyword throw. As such, another keyword should be used, such as throws. By the way, throws with the “s” is also the original proposal! That new keyword would be in line with other keywords, like implements and extends. E.g.

function foo(): void throws Error {
  throw new Error();
}

// and the original example still works without modifications

type MyCallback = (x: number)
  => void
  throw Error;

// or could be changed to instead be part of the type

type MyCallback = (x: number)
  => void
  throws Error;

Typos in this context are similar to typos in other contexts and can be prevented by using a linter and enforcing semicolons.

I think this would be a very monumental addition to the language and fix one of the biggest flaws of Javascript. It would enable typescript to be used in more use cases where comprehensive exception handling is a necessity. IMO it’s the current most important deficiency to address in the language.

Honestly, I don’t care about the Error type (I still do care about it though) as much as I care about the information whether the function definitely throws sometimes or not.

I don’t remember the implementation of all functions in our repo and it’s tedious to always have to look them up.

Consider this consumer code

async function handleUserAction(event) {
  if (event.detail.id) {
    await loadSomeStuff(id)
    scheduleRender()
  } else {
    const otherStuff = await fetchSomeStuff(otherId)
    doSomethingSlightlyDifferent(otherStuff)
    toggleUI()
  }
}

🤔 LGTM 🤔 during code review 🤔

B U T !!i!i!i!i!i!i!i!i!!

async function fetchSomeStuff(id) {
  const response = await fetch(`api.example.com/${id}`)/*...*/
  if (isValid(response)) {
    return /* xyz */
  }
  throw new Error('NOPE')
}

async function loadSomeStuff(id) {
  try {
    const someStuff = await fetchSomeStuff(id)
    setSomeStuff(someStuff)
  } catch {
    setSomeStuff({})
  }
}

I would LOVE to have been (or the other developer) notified about it

async function handleUserAction(event) {
  if (event.detail.id) {
    await loadSomeStuff(id)
    scheduleRender()
  } else {
    const otherStuff = await fetchSomeStuff(otherId) // warn: this function can throw actually
    doSomethingSlightlyDifferent(otherStuff)
    toggleUI()
  }
}

Hegel seems to have this feature perfectly. https://hegel.js.org/docs#benefits (scroll to “Typed Error” section)

I wish TypeScript has similar feature!

Thank you very much for your positive feedback! I realise it could be easier to refactor existing code without having to wrap the entire returned type into the throwable type. Instead we could just append that one to the returned type, the following code let you append the throwable errors as follow:

// now only append '& throwable<ErrorsThrown>' to the returned type
function unreliableNumberGenerator(): number & throwable<Error | CustomError> { /* code */ }

This is the only change for the example part, here is the new types declaration:

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<E extends Error> = extraType<E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;

type exceptionsOf<T extends basicFunction> = getExtraType<ReturnType<T>>;
 
const getTypedError = function<T extends basicFunction> (error: unknown, throwableMethod:T) {
    return error as exceptionsOf<T>;
};

I also added a new type exceptionsOf which allows to extract the errors of a function in order to escalate the responsibility. For instance:

function anotherUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : 100;
}

As exceptionsOf gets a union of errors, you can escalate as many critical method as you want:

function aSuperUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator> | exceptionsOf<typeof anotherUnreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : unreliableNumberGenerator();
}

I don’t like the use of typeof, if I find a better way I will let you know

You can test here the result tips: hover typedError at line 106

i see what you are saying, nothing is going to be changed at javascript runtime, however your message here is to give users some illusion that they know what they are doing by handling an exception that came from 20 layers down below with the same confidence as they would handle an immediate exception

there is simply no way they can fix a problem that happened 20 layers down below

you can log it, sure, just as any unchecked exception, but you cannot fix it

so it’s a lie generally speaking, there is enough lies in TS, let’s not confuse people even more

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!

+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)")
    }
}

@polkovnikov-ph

Imagine you have […] to specify that forEach doesn’t consume any of those errors inside of it.

With “just” support, this doesn’t matter. It’s the implementor’s task to add throw clauses to the function. Adding it to the std would likely mean a lot more discussion around a lot of things, and imho it is best to PoC that. Which is only possible with initial support. The problem with trying to get everything right before doing anything is that topics become stale and you’ll never reach a state where you can move foreward with anything out of sheer fear of the unknown problems and errors awaiting, unsure what changes might bring to the table. Has history tought us that stalling brings us to anything but stagnation? No!

The silence itself is not a problem, it usually means people are working. In fact, newly opened tickets get triaged in only a day, which means developers are actually engaged with this bug tracker. The problem is that we have no idea where in backlog those tickets are, and even if there is any backlog at all.

The silence means lack of communication. If peope are working on it, then that would be worth a statement. Not writing anything, at least where I’m from, means nothing is happening, nothing is done. Experience tought me that, and until now, silence here has always meant that nothing is happening from the TS team’s side 😦 So I’ll have to make noise myself…

@bensaufley does it matter if its trivial or not?

I wouldn’t want to add it on every single one of my projects that need it (or depend on libraries like type-fest) when ReturnType is already there natively.

So I was wondering if it would make sense to add it for parity.

My only concern is this:

const getJson = (ur) throws Error : Promise<string> => {
  // something bad happened and
  throw new Error("bad sync error");

  return Promise.reject("async error"); // or a more complex promise with a possible rejection 
}

In that function (and all functions that return promises) there’s a possibility of the function to “sync” throw and to async reject, but we only types the function with throw. How will the dev know if a function can throw or just reject ? Because this:

getJson("something").catch(err => console.log("we caught it!", err))

This will throw with something like Error: bad sync error, but it won’t reject.

Should we add a difference between throwing and rejecting ? Since one or the other can happen for the same function. Or maybe I’m just saying none sense 😛

Do you mean something like this:

function foo(): void throws string {
    throw '';
}

async function bar() /* : Promise<void> */ {
    foo(); // in async contexts, throws are converted to rejections, that's how JS works!
}

bar()
    .then(function() {})
    .catch(function(reason /* : unknown */) {});

image

At the moment, Promises don’t have rejection types, and I really think that rejection types should be a separate issue. For all we care, the typing is lost. I strongly believe both parts should be implemented and released at the same time, though 😃

function foo(): void throws string {
    throw '';
}

async function bar(): Promise<void> {
    return Promise.reject(42);
}

async function baz(): Promise<void> {
    try {
        foo();
    } catch (e /* : string */) {
        assert(typeof e == 'string');
    }

    try {
        await bar();
    } catch (e /* : unknown */) {
        // ...
    }

    try {
        foo();
        await bar();
    } catch (e /* : unknown */) { // e will always be unknown with async!
        // ...
    }
}

Both specs can land in the same TS release, but discussion should be separate, right?

So, the rest of the possible combinations would be:

function foo1(): void throws string {
    throw ''; // OK
}

function foo2(): void throws string {
    return; // ERROR: throw declared but not implemented
}

function foo3(): Promise<void> throws string {
    throw ''; // OK
    return Promise.reject(''); // ERROR: code not reachable
}

function foo4(): Promise<void> throws string {
    if (Math.random() > 0.5)
        throw ''; // OK
    return Promise.resolve(); // OK
}

async function foo5(): void throws string { // ERROR: wrong return type, must be Promise
    // ...
}

async function foo6(): Promise<void> throws string { // ERROR: async functions cannot declare sync throws
    throw ''; // OK, will be a Promise rejection!
}

ATTENTION: NOT PART OF THIS ISSUE! Imho this should be a follow-up and separate, so we don’t mix stuff up. With typed throws, we may have rejection types for Promises:

function foo() /* : void throws string */ {
    throw 'foo';
}

function bar() /* : Promise<void, string> throws string */ {
    if (Math.random() > 0.5)
        foo();
    return Promise.reject('bar');
}

try {
    bar()
        .then(function() {})
        .catch(function(reason /* : string */) { assert(reason === 'bar'); });
} catch(reason /* : string */) {
    assert(reason === 'foo');
}

While the exact signature seems to not be entirely set in stone yet (might need a decision from the team), what’s blocking this issue from being implemented?

@mckravchyk with some small change we can eliminate the need for GetFunctionResult:

-type Throws<T> = { throws: T };
+type Throws<T> = { __phantom_type_throws__?: T & never };
 
 type GetThrow<T> = T extends Throws<infer R> ? R : never;
-type GetResult<T> = T extends Throws<unknown> ? never : T;
-
 type GetFunctionThrow<T extends (...args: any[]) => any> = GetThrow<ReturnType<T>>;
 
-// This type is needed to eliminate the Throws type from the union so the return
-// value can be used properly
-type GetFunctionResult<T extends (...args: any[]) => any> = GetResult<ReturnType<T>>;
-
 type CalcError = {
     code: "calc_error"
     message: string
@@ -19,7 +13,7 @@
     message: string
 }
 
-function divide(a: number, b: number): number | Throws<CalcError | DivisionError> {
+function divide(a: number, b: number): number & Throws<CalcError | DivisionError> {
     if (b === 0) {
         throw { code: "division_error", message: "Cannot divide by 0" } as DivisionError; 
     }
@@ -27,7 +21,7 @@
     return a / b;
 }
 
-function calc(a: number, b: number, operation: 'divide'): number | Throws<CalcError | DivisionError> {
+function calc(a: number, b: number, operation: 'divide'): number & Throws<CalcError | DivisionError> {
     if (typeof a !== 'number' || typeof b !== 'number') {
         throw { code: "calc_error", message: "Required 2 number arguments" } as CalcError;
     }
@@ -42,7 +36,7 @@
 let x: number;
 
 try {
-    x = calc(5 ,6, 'divide') as GetFunctionResult<typeof calc>;
+    x = calc(5 ,6, 'divide');
 } catch (e: unknown) { // Catch-clause annotation must be any or unknown
     const err = e as GetFunctionThrow<typeof calc>; // CalcError | DivisionError
 }

🔗 Playground link

but what’s the proper typing supposed to be between async/await and Promise<V, E> ? i think it needs more designs and discussions

What do you mean here? Basically, async function can’t throw sync errors. It can deal only with promises and their resolvability and rejectablility!

Consider this example:

async function caller() {
  try {
    testSyncErrorInAsyncFunction()
  } catch(e) {
    // haha it is not really sync error
    console.log('sync error cought', e)
  }

  try {
    await testSyncErrorInAsyncFunction()
  } catch(e) {
    // you will see only this error message
    console.log('async error cought', e)
  }
}

async function testSyncErrorInAsyncFunction() {
  console.log('executing async function')
  // woops! error is not sync. It is async!
  throw new Error('sync error')
}

caller()

Async functions are typed as regular function but with exception that async function can return only promise. So typing async/await will reduce to async function myFunc(arg1, arg2): Promise<Return, Error>. No other form should be allowed!

I think the throws clause should be completely invalid with the current async syntax. Unless it was changed so that when specifying async, you should unwrap the Promise return type.

Meaning, it does not make sense to try to make a function’s type signature try to appear both sync and async, when async is specified.


Though with all this talk about Promise monads, I do think this could be the right time to allow enforcement of unwrapped async result types.

Take the following for example.

This is how things are today:

function myFunc1(arg: any): Promise<'async return'> | 'sync return' {
    if (arg == null) throw 'sync error';
    if (arg == 1) return Promise.reject('async error');
    if (arg == 2) return Promise.resolve('async return');
    return 'sync return';
}
async function myFunc2(arg: any): Promise<'async return'> {
    if (arg == null) throw 'async error';
    if (arg == 1) return Promise.reject('async error');
    if (arg == 2) return Promise.resolve('async return');
    return 'async return';
}

Now the problem with introducing throws with the current design, is because myFunc1 throws a 🔧 into that.

So now the only way to satisfy things in the current design is what has been described as:

function myFunc1(arg: any): throws 'sync error' : Promise<'async return', 'async error'> | 'sync return' {
    ...
}
async function myFunc2(arg: any): throws 'async error' : Promise<'async return', 'async error'> {
    ...
}

What I am saying is, to make this all make sense to me, I’d want to always require unwrapped results when async is specified, for example, if we change what we have today to:

function myFunc1(arg: any): Promise<'async return'> | 'sync return' {
    ...
}
// NOTE NO Promise SPECIFIED HERE!
async function myFunc2(arg: any): 'async return' {
    ...
}

Then, this would allow the new throws clause to be consistent and work like this:

// With no async, it supports wrapped and unwrapped results
function myFunc1(arg: any): throws 'sync error' : Promise<'async return', 'async error'> | 'sync return' {
    if (arg == null) throw 'sync error';
    if (arg == 1) return Promise.reject('async error');
    if (arg == 2) return Promise.resolve('async return');
    return 'sync return'
}
// With async it supports *only* unwrapped results
async function myFunc2(arg: any): throws 'async error' : 'async return' {
    if (arg == null) throw 'async error';
    if (arg == 1) return Promise.reject('async error');
    if (arg == 2) return Promise.resolve('async return');
    return 'async return'
}

I feel I should note, awaiting either of those functions yields the same behavior, each throw or return with the same flow. The only time the behaviors of these functions are different, is without invoking with await (i.e. without unwrapping the result).


Yes, this is all a big major breaking change for async functions, but hopefully it could be supported like all the other changes like this, with a compilation flag. i.e. enforceAsyncUnwrappedResult (or something).


Final point

This:

async function myFunc2(arg: any): throws 'async error' : 'async return' {

is really sugar for this:

async function myFunc2(arg: any): Promise<'async return', 'async error'> {

Which basically means, it’s back to my first statement. Make throws completely invalid when async is used, and doing that, it would futureproof a path to a flag like this.

This will also be useful for compiler to infer unreachable code if the function is certain to throw. For example:

const alertAndThrow = (message: string): never => {
    alert(message);
    throw new Error(message);
};

// ...

alertAndThrow("foo");
const x = 5; // <--- unreachable, but currently the compiler is not able to infer this

Maybe consider supporting jsdoc @throws alert first https://github.com/microsoft/TypeScript/issues/43528

I will repeat here what I said in the now committed issue.


I think that typescript should be able to infer the error type of most of the JavaScript expressions. Allowing for faster implementation by library creators.

function a() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
}

function b() {
  a();
}

function c() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
  if (Math.random() > .5) {
    throw 'fairly lucky';
  }
}

function d() {
  return eval('You have no IDEA what I am capable of!');
}

function e() {
  try {
    return c;
  } catch(e) {
    throw 'too bad...';
  }
}

function f() {
  c();
}

function g() {
  a();
  c();
}
  • Function a we know that the error type is 'unlucky', and if we want to be very cautious we can extend it to Error | 'unlucky', hence includeError.
  • Function b will inherit the error type of function a.
  • Function c is almost identical; 'unlucky' | 'fairly lucky', or Error | 'unlucky' | 'fairly lucky'.
  • Function d will have to throw unknown, as eval is… unknown
  • Function e catches the error of d, yet since there is throw in the catch block, we infer its type 'too bad...', here, since the block only contains throw 'primitive value' we could say it cannot throw Error (Correct me if I missed some JS black magic…)
  • Function f inherits from c same as b did from a.
  • Function g inherits 'unlucky' from a and unknown from c thus 'unlucky' | unknown => unknown

Here are some compiler options I think should be included, as users engagement with this feature may vary depending on their skill as well as type-safety of libraries they depend on in given project:

{
  "compilerOptions": {
    "errorHandelig": {
      "enable": true,
      "forceCatching": false,   // Maybe better suited for TSLint/ESLint...
      "includeError": true, // If true, every function will by default throw `Error | (types of throw expressions)`
      "catchUnknownOnly": false, // If true, every function will by default throw `unknown`, for those very skeptics (If someone replaces some global with weird proxy, for example...)
      "errorAsesertion": true,  // If false, the user will not be able to change error type manually
    }
  }
}

As for syntax on how to express the error any function can produce, I am not sure, but I know we need the ability for it to be generic and inferable.

declare function getValue<T extends {}, K extends keyof T>(obj: T, key: K): T[K] throws void;
declare function readFile<throws E = 'not valid file'>(file: string, customError: E): string throws E;

My use-case, as showing an actual use-case might show other it has a value:

declare function query<T extends {}, throws E>(sql: string, error: E): T[] throws E;

app.get('path',(req, res) => {
  let user: User;
  try {
    user = query('SELECT * FROM ...', 'get user');
  } catch(e) {
    return res.status(401);
  }

  try {
    const [posts, followers] = Promise.all([
      query('SELECT * FROM ...', "user's posts"),
      query('SELECT * FROM ...', "user's follower"'),
    ]);

    res.send({ posts, followers });
  } catch(e) {
    switch (e) {
      case "user's posts":
        return res.status(500).send('Loading of user posts failed');

      case "user's posts":
        return res.status(500).send('Loading of user stalkers failed, thankfully...');

      default:
        return res.status(500).send('Very strange error!');
    }
  }
});

I need an error sink to prevent sending headers of response multiple times for multiple errors (they usually don’t happen, but when they do they do so in bulk!)

I also just wanted to drop my thoughts in on this.

I think this would be a really good feature to implement as there are different use cases for wanting to return an Error/null and wanting to throw something and it could have a lot of potential. Throwing exceptions is part of the Javascript language anyway, so why shouldn’t we give the option to type and infer these?

For example, if I’m doing lots of tasks that could error, I’d find it inconvenient to have to use an if statement to check the return type each time, when this could be simplified by using a try/catch where if any one of these tasks throws, it’ll be handled in the catch without any additional code.

This is particularly useful when you’re going to be handling the error’s in the same way; for example in express/node.js, I might want to pass the error to the NextFunction (Error handler).

Rather than doing if (result instanceof Error) { next(result); } each time, I could just wrap all of the code for these tasks in a try/catch, and in my catch I know since an exception was thrown I’ll always want to pass this to my error handler, so can catch(error) { next(error); }

Also haven’t seen this discussed yet (May have missed it however, this thread has quite a few comments!) but if this was implemented, would it be made mandatory (i.e: Compilation error) to have a function that throws without using the throws clause in its function declaration? I feel like this would be nice to do (We’re not forcing people to handle the throws, it would just inform them that the function does throw) but the big concern here is that if Typescript was updated in this way, it would likely break lots of currently existing code.

Edit: Another use-case I thought of could be this would also help with generation of JSDocs using the @throws tag

Hey! Thanks for responding! Actually, having the throws inferred as you mentioned mitigates this issue I was thinking of. But the second example you listed is interesting.

Given

function first() : string throws MyVeryImportantError { // Compiler complains: missing number and string
    if(something) {
        throw MyVeryImportantError
    } else {
        return second().toString();
    }
}

function second() : number {
    if(something)
        throw 5;
    else   
        return third();
}

function third() : number throws string {
    if(!something)
        throw 'oh no!';
    return 9;
}

For me to explicitly declare MyVeryImportantError, I would have to also explicitly declare all the additional errors of the callstack, which could be a handful depending on the depth of the application. Also, potentially tedious. I certainly would not want to dive down the entire call chain to generate a list of potential errors which could arise on the way, but perhaps the IDE could help.

I was thinking of proposing some sort of spread operator to allow the developer to explicitly declare their error and just throw the remainder up.

function first() : string throws MyVeryImportantError | ... { // Spread the rest of the errors

But there would be an easier way to achieve the same result: just drop the throws declaration.

function first() : string { // Everything automatically inferred

Which raises the question: when would I see a benefit to using the throws keyword rather than just letting typescript infer the errors?

@ConnorSinnott I hope that I understand you right:

The compiler will infer thrown types, for example:

function fn() {
    if (something) {
        throw "something happened";
    }
}

Will actually be function fn(): throws string { ... }. The compiler will also error when there’s a mismatch between the declared errors and the actual ones:

function fn1() throws string | MyError {
    ...
}

function fn2() throws string {
    fn1(); // throws but not catched

    if (something) {
        throws "damn!";
    }
}

The compiler should complain that fn2 throws string | MyError and not string.

With all of this in mind, I don’t see how this assumption is more dangerous than the assumption that other declared types that a developer trusts when using other libraries, frameworks, etc. I’m not sure I really covered all the options, and I’ll be glad if you can come up with an interesting scenario.

And even with this problem, it’s pretty much the same as it is now anyway:

// can throw SyntaxError
declare function a_fancy_3rd_party_lib_function(): MyType;

function fn1() {
    if (something) {
        throws "damn!";
    }

    if (something else) {
        throws new Error("oops");
    }

    a_fancy_3rd_party_lib_function();
}

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") { ... }
        else if (e instanceof MyError) { ... }
    }
}

We can have typescript guard us even from this, but it will require a syntax which is a bit different from javascript:

function fn2() {
    try {
        fn1();
    } catch(typeof e === "string") {
        ...
    } catch(e instanceof MyError) {
        ...
    }
}

This will be compiled to:

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") {}
        else if (e instanceof MyError) {} 
        else {
            throw e;
        }
    }
}

@nitzantomer I completely agree that this feature is a necessity for typescript. My library is nothing more than temporary stand-in until this feature is added.

I’d like to add in my +1 for typed and checked exceptions. I’ve made great use of typed exceptions in my code. I’m fully aware of the pitfalls of instanceof, I’ve actually used it to my advantage by being able to write generic handlers for related errors that inherit from a common base class. Most other methods I’ve encountered to avoid inheritance from the base Error class end up being ( at least ) equally complex and problematic in different ways. Checked exceptions are an improvement that in my opinion lends itself to greater static analysis insight into the codebase. In a codebase of sufficient complexity it might be easy to miss what exceptions are thrown by a particular function.

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.

@hyurl I think it could be an option to require handling of errors (since I see some people don’t want this functionality, and I for example do, so I can imagine others may too). When you have that enabled, in case of bubbling, you would be required to either add throws to your function (to show that it can throw an error because it calls another method that may) or to handle the error (this is pretty much how it works in Java).

However, when you don’t have that enabled, I think it would be perfectly OK if “bubbling” errors lose their types if the parent methods don‘t have throws.

Example:

class A {
    public static method foo(): "bar" throws Error {
        if (Math.random() > 0.5) throw new Error("no luck");
        return "bar";
    }

    public static method baz(): void {
        this.foo();
    }
}

With required error handling, you would be forced to add throws Error to baz() or surround this.foo() in try-catch. I personally like this approach as it ensures all errors are handled, leading to no unexpected crashes due to unhandled exceptions, and also makes the throw types explicit.

Without this requirement, a warning could be shown on the line with this.foo() suggesting that the error be handled (perhaps only on the first level of not handling an error from a method with throws so it‘s not too annoying for people who really don‘t want this). Or perhaps just leave the throws type of baz() as unknown or any (depending on existing setting) as if you wanted to carry the throws type you could add it yourself. I am not sure, as I would not use this mode.

Similarly to how there is the ! suffix that can be used to cast to a type without null and undefined, there could be another mark to remove throws in case you are sure the method will not error this time and you really don’t want to write a try-catch just in case it actually does. Since both ? and ! already have a use, maybe it could be !! and that should cause no conflict with the existing ! since (undefined!)! makes no sense (and produces never).

On another note: https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhjAvDAFASiQPhgbwFAyEwBOAplAK7FgwCycUAFgHTFxgAmIAtujNgAZmAVhgB+GACIAZiBCSYALikAjOMUkBuPAF88eUJFgqkqDImxx02w9BjBTfCzBXWD4Ox0fnswN7dhSbywYDn8PWGlg51JwoxgAc2jsaTi7RmTE6xgAehyYJiCySmoCgE8AByCASwgYaGqAG0apWXkYAB9VdUkgA

In this TS code, you have similar functionality with “bubbling” of a return type. You can hover over h to see that the type is preserved and this makes me think that similar functionality can be achieved with throws as well.

@polkovnikov-ph your assertion is basically arguing that the perfect must be the enemy of the good. You say that e.g. forEach would “have to” specify that it does not consume the throws of its callback. That would only be true if we say that the standard library must propagate throws information accurately everywhere on the first release of the feature. Many people have argued throughout the comments on this issue that this should not be the case. Shipping accurate typings on the first pass is simply too big a bite to take; exposing some accurate throws information, some of the time, is still more useful than never exposing any throws information at all.

I would argue that Array#forEach is probably low-hanging fruit, in this case, and could be pretty accurate on the first go, using a generic, but that’s an implementation detail. The broader point stands, though, that shipping any ability to expose “expected” throws, would allow us to iteratively improve that throw information. The only caveat is that the documentation would have to make it very clear that lack of throws annotation is not a guarantee that something does not throw. The default should be throws any (or throws unknown under --useUnknownInCatchVariables), then individual annotations can be improved incrementally.

Unknown errors in 3rd-party libs can be solved with input validation


const somethingWithWrapper = () => {
  try {
    // Imported from 3rd party lib without types
    something()
  } catch (error) { // error is `unknown` at this point
    // Ensure that it's known error
    if (error instanceof TypeError) {
      throw error
    }

    // ... or if it's something unknown, wrap in own error
    throw new MyError(
      'Something is wrong',
      error // cause
    )
  }
}

try {
  somethingWithWrapper()
} catch (error) { // compiler now knows it's TypeError | MyError 
  // ...
}

I know the typescript team are hesitant to add compiler flags, but surely this is a great use case for them?

Do you want to add strictness for all uncaught, known errors that make it up to the top of the callstack? Use an opt in flag.

The folk who don’t care can then safely ignore this and still benefit from type inference and suggestions without even realising it.

@jimisaacs I think the argument was made some pages back that we aren’t trying to implement an untyped throws clause because that would imply that a function which is not decorated with throws, cannot ever possibly throw, and it’s usually unreasonable to try to prove this to the typechecker. If anything, it might be helpful to have a keyword that decorates a function that absolutely cannot ever throw (or a Promise that absolutely cannot ever reject), but that basically means

function f(...): void throws never {
  try {
    ... // Actual logic
  } catch (e) {
    ... // only calling other `throws never` functions
  }
}

or its async equivalent. Instead, as Mr. Rosenwasser said way back at the beginning, the gist is

not to force users to catch the exception, but rather, to better infer the type of a catch clause variable

Re @maugenst , the issue is currently tagged “Awaiting Feedback”. 5 years and well over 100 posts does seem like an awfully long time to collect feedback, though. I would be interested in knowing how the team picks issues to discuss in their monthly meetings. You would think nearly a thousand thumbs-ups would grab some attention, right?

You know in the grand scheme of things, I don’t really think this proposal needs typed errors at all. I don’t think typed errors are actually the primary value for a proposal like this.

All I really think I want, is a simple hint that a function can throw, or not. i.e. the Swift version of the following…


For a standard function that is updated to look like this:

declare var URL: {
    // ...
    new(url: string | URL, base?: string | URL): URL throws; // <- THIS THROWS!!!
};

Now, take the following user-land snippet:

const parseURL = (str: string) => URL {
    return new URL(str); // <- marked with throws, THIS IS A TS ERROR!!!!
};

It needs to be fixed to look either like this:

const parseURL = (str: string) => URL | undefined { // <- Needed to add undefined here
    let url: URL | undefined;
    try {
        url = new URL(str); // <- This is ok now, we are wrapped in a try/catch block
    } catch (err) {
        // noop
    }
    return url;
};

Or like this:

const parseURL = (str: string) => URL throws { // <- Added throws here
    return new URL(str); // <- This is ok now because our executed scope is marked with throws
};

This is a more contrived yet complex example:

const parseURL = (str: string) => URL throws { // <- Added throws here, but...
    return (() => { // <- This is a self executing function
        return new URL(str); // <- Because this is marked with throws... THIS IS STILL A TS ERROR!!!
    })();
};

The inner context needs some love too:

const parseURL = (str: string) => URL throws { // <- Added throws here
    return (() throws => {  // <- Added throws here too
        return new URL(str); // <- This is ok now because all executed scopes are marked with throws
    })();
};

Ok, and here is one more example with a callback factory:

const createParseURL = (str: string) => () => URL throws { // <- the createParseURL function does NOT need throws
    return () throws => {  // <- We only need throws here
        return new URL(str); // <- This is ok because only the executed scope needs marked with throws
    };
};

Yes I realize it’s missing the types, but I’d argue it still holds immense value, as it has been proven to me with other languages, namely Swift. The difference in the above example versus Swift is that Swift comes with try, try? and try! to denote how you intend to handle the error, either by propagating it, handling it, or crashing. I could be wrong, as I’m not a language designer, but I don’t think this needs that here?

I am really very excited to see that there is interest for a “throws” clause. It’s absolutely critical for secure development, to detect and avoid unhandled throws.

Regarding throws generated by awaited promises, unknown type seems appropriate, until rejection types are a thing.

@Ranguna I guess we need to add error type to Promise as second optional type parameter like:

const getJson = (url) throws Error : Promise<string, ErrorType> => {
  // something bad happened and                      ^^^^^^^^^
  throw new Error("bad sync error");

  return Promise.reject("async error"); // or a more complex promise with a possible rejection 
}

This sample tell the dev that it can throw sync error or reject promise with error

@eczn not a particular fan of >> instead of of simpler throws token. Other than that, this looks good.

declare async function getJson(url: string) throws HttpErr : Promise<string>;

My only concern is this:

const getJson = (ur) throws Error : Promise<string> => {
  // something bad happened and
  throw new Error("bad sync error");

  return Promise.reject("async error"); // or a more complex promise with a possible rejection 
}

In that function (and all functions that return promises) there’s a possibility of the function to “sync” throw and to async reject, but we only types the function with throw. How will the dev know if a function can throw or just reject ? Because this:

getJson("something").catch(err => console.log("we caught it!", err))

This will throw with something like Error: bad sync error, but it won’t reject.

Should we add a difference between throwing and rejecting ? Since one or the other can happen for the same function. Or maybe I’m just saying none sense 😛

v8 is adding error causes which can be another way to identify potential errors/ exceptions. Potentially that can be used to perform type analysis on the error types without creating novel classes.

function doWork() {
  try {
    doSomeWork();
  } catch (err) {
    throw new Error('Some work failed', { cause: err });
  }
  try {
    doMoreWork();
  } catch (err) {
    throw new Error('More work failed', { cause: err });
  }
}

try {
  doWork();
} catch (err) {
  switch(err) {
    case 'Some work failed':
      handleSomeWorkFailure(err.cause);
      break;
    case 'More work failed':
      handleMoreWorkFailure(err.cause);
      break;
  }
}

@agonzalezjr I think that like most features in typescript you should be able to opt-in with this feature as well. Just like it’s not mandatory to add types, it shouldn’t be a must to throw/catch.

There probably should be be a flag to make it a must, like --onlyCheckedExceptions.

In any case, this feature will also be used to infer/validate the types of thrown exceptions, so not just for documentation.

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

Everything can throw anytime, its nature of JS and I don’t know when you can be sure, that function does not throw.

(null as any).nonExistent;

Of course, Java has the same issue and they solved it with checked and unchecked exceptions.

As much as I want a feature like this, coming from the Java world where many people mostly stopped using checked exceptions, I’m pretty skeptical that there’s a clean way to handle type-safe exceptions that will be ergonomic to use and therefore get widely adopted.

Notably, the designers of Kotlin, who were trying to make a better, safer Java chose not to include checked exceptions (https://kotlinlang.org/docs/exceptions.html). It seems like there’s a pretty high barrier to a good design for type-safe error handling in a language with throw.

Its arguable that the amount of typing and meta programming needed to fully express how errors will propagate simply isn’t worth the benefit. There will be so many places where methods have to throw unknown or similar. Though, I imagine the same could have been said when null-safety was being considered, and that worked out rather well.

@macsikora hi, I don’t see why this would not be possible. The typed errors don’t have to be exhaustive. Anytime you or a dependency throw something in the code you can type it even if this is just a string. Unknown errors just won’t be part of the errors we can handle. This is even how Java is working as far as I can remember.

i just realize someone else thought my same idea but is a little more elaborated here some examples

import { Ok, Err, Return } from '@kraftr/errors'
import db, { Connection, ConnectionError } from 'any-db'

function connectToDb(): Return<Connection, ConnectionError> { // could be Connection | Throws<ConnectionError>
  return db.connect('url') // can throw error
}

const connection = shelter(connectToDb) //  OkErr<Connection, ConnectionError>
if(connection.isErr) {
  connection.error // properly typed as ConnectionError
}
connectToDb.table('') // intellisense and everything just works
type ErrorType = ErrorMetadata<typeof connection> // ConnectionError
type ValueType = ValueMetadata<typeof connection> // Connection

more examples in the repository: https://github.com/kraftrio/kraftr/tree/develop/core/errors

I even took it further and i created a eslint rule to enforce type functions

function connectToDb(): Connection {
  if(xxx) {
      throw new Error('') // lint error "the function should indicate that it's possible throw an error"
  }
  return db.connect('url')
}

i just need to auto fix the rule to auto add the collected errors types

playground

Btw I think it’s a good idea to force error handling with a separate compiler flag when throws operator would finally be implemented.

@roll there shouldn’t be any “guaranteeing”. it’s not a must to handle exceptions in javascript and it shouldn’t be a must in typescript as well.

TBH I think it’s more important to have some mechanism guaranteeing handling exceptions (ala not used error in Go) rather than providing type hints for try/catch

@vultix The approach of your lib has been discussed above, and it’s the exact opposite of what this feature is here to solve. Javascript already has the mechanism for handling errors, it’s called exceptions, but typescript lacks a way of describing it.

Yeah, I know how JavaScript works, we are discussing TypeScript which emposes further limitations. I suggested, a good solution IMHO would be the make TypeScript follow the exception handling that requires all thrown exceptions to be of a specific base class and not to allow throwing unwrapped values directly.

So, it won’t allow “throw 0” or “throw ‘some error’”. Just like JavaScript allows many things TypeScript doesn’t.

Thanks,

@aluanhaddad

This suggestion doesn’t propose to add any new functionality, only to add a way to express in typescript something that already exists in javascript. Errors are thrown, but currently typescript has no way of declaring them (when throwing or catching).

As for your example, by catching the error the program can fail “gracefully” (for example showing the user a “something went wrong” message) by catching this error, or it can ignore it, depending on the program/developer. If the programs’ state can be affected by this error, then handling it can keep a valid state instead of a broken one.

In any case, the developer should make the call of whether he can recover from a thrown error or not.
It’s also up to him to decide what it means to recover, for example if I’m writing this http client to be used as a 3rd party library, I might want all errors thrown from my library to be of the same type:

enum ErrorCode {
    IllFormedJsonResponse,
    ...
}
...
{
    code: ErrorCode;
    message: string;
}

Now, in my library when I parse the response using JSON.parse I want to catch a thrown error and then throw my own error:

{
    code: ErrorCode.IllFormedJsonResponse,
    message: "Failed parsing response"
} 

If this feature is implemented then it will be easy for me to declare this behavior and it will be clear to the users of my library how it works and fails.

if every layer is bound to take responsibility of reacting to an exception coming immediately from a layer below, there is a much better chance for a successful recovery, this is the idea behind the checked exceptions as i see it

to put it in different words, exceptions coming from more than 1 level below is a sentence, it’s too late to do anything other than re-instantiating all infrastructure from ground up (if you are lucky enough there are no global leftovers that you can’t reach)

proposal as stated is mostly useless, because there is no reliable way to react to the knowledge of something bad happened outside of your reach

you can’t handle a problem happened 20 layers below gracefully, because the internal state is corrupt in 19 layers and you can’t go there because the state is private

to be constructive: what i am suggesting is to require users handle checked exceptions immediately and rethrow them explicitly, this way we rule out unintended confusion and separate checked exceptions from unchecked:

  • checked exception: happened in the immediate reach and must be handled, this way it’s guaranteed that the state isn’t corrupt and it’s safe to proceed
  • unchecked exception: happened in the immediate reach or much further down, it cannot be handled because the state was corrupt, you can log it or proceed on your own risk

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

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 (and maybe the only step needed to satisfy reconsideration point 1).

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

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.

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

@acomagu it sounds like you’re making a good case for erring on the side of fewer throws declarations in the core lib, to avoid excess noise. As @ianldgs points out, you might be able to reduce the chances of a false positive (i.e., a function declared as throws Foo that could be determined never to throw by a sufficiently-advanced type checker) with clever overloads. But if it becomes too convoluted to do that, I think it would still be better to have one simple declaration that misses a possible (but rare) exception.

Also, re:

function Foo(arr: unknown[]): void throws never { // No compilation error
  if (!arr.length) return;
  JSON.stringify(arr);
  arr.reduce(() => {});
}

Foo([1n]); // TypeError: BigInt value can't be serialized in JSON

I think we can probably agree that JSON.parse and JSON.stringify should be declared as throws TypeError, which would mean that your function should be flagged as an error by a sensible implementation of this feature. You declared a function as throws never, but it calls a throws TypeError method (JSON.stringify) outside of a try-block. That’s exactly the kind of mistake we want to identify.

@acomagu of course, there would be limitations. But as someone said above, do not make perfect the enemy of good.

lib.es5.d.ts:

reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T throws TypeError;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; // doesn't throw

And just supply the initialValue on the reduce call to get rid of the throws. I would say that annotating a function with throws never would be the equivalent of using as unknown as T or as any, which no one seems to be complaining.

I think it’s really hard to maintain throws part of a function.

For example, Array#reduce can throw TypeError when the receiver array is empty array(and initial value is not provided).

[].reduce(() => {}); // => TypeError: reduce of empty array with no initial value

So its type definition would look like this:

interface Array<T> {
  reduce( ... ): T throws TypeError;
}

And if you define a function using it, TypeError will “bubble”, if you don’t give explicit throws information.

function Foo(arr: unknown[]): void { // Foo throws TypeError
   arr.reduce(() => {});
}

But we can add checking before calling arr.reduce to avoid the error. That is, a check to ensure that arr is not empty. In this case, TypeError is not possible, so we explicitly write throws clause to clarify that no error will occur.

function Foo(arr: unknown[]): void throws never {
  if (!arr.length) return;
  arr.reduce(() => {});
}

But what if we accidentally delete that check?

function Foo(arr: unknown[]): void throws never { // No compilation error
  arr.reduce(() => {});
}

Foo([]); // TypeError: reduce of empty array with no initial value

Or if we add another process can throw error to the function?

function Foo(arr: unknown[]): void throws never { // No compilation error
  if (!arr.length) return;
  JSON.stringify(arr);
  arr.reduce(() => {});
}

Foo([1n]); // TypeError: BigInt value can't be serialized in JSON

In both cases, we can’t notice the mistake because no compilation error will occur.

Everything can throw anytime, its nature of JS and I don’t know when you can be sure, that function does not throw.

(null as any).nonExistent;

For me will be enough to have info about all kind of exceptions in declaration files (ex: lib.dom) and to have an option to check if there are some uncatched exceptions in my code. This will not require creators to update their code. This will not force developers to catch all exceptions. But this will make my code to work better and not to die in runtime because of some unexpected exceptions in unexpected places.

I’m trying to recall what flow control issue it was failing to detect, but now I can’t make it happen. Looks like I was mistaken about that. My appologies.

Typed errors would be the major benefit of this feature in my opinion / experience, and going so far as to implement throws without that seems like it would be a weird half-measure. I agree that having a simple throws without typing would at least risk implying that anything that isn’t marked throws does not then throw, which is a dangerous road to go down. In fact I would think that it might be reasonable to have a default behavior of throws unknown on functions that do not have explicit error typing, though likely via a strict-type tsconfig option because that would definitely open a can of worms.

I think I disagree with this:

are you saying that currently, the typechecker must assume that anything can throw? I’m sorry, but that is not how people actually use this language and write code today.

I can’t speak for how people, broadly, use the language, but I would say I certainly write my code with an assumption that most functions can throw and that at least somewhere up the call stack I’ll need to plan for that.

I do like Swift’s try helpers and I would love that to be added as syntactic sugar, but I almost think that’s the kind of thing you’d want to see in JS (as it doesn’t rely on typing) before it found its way to TS.

@thw0rted

throws would definitely have to be a reserved word.

Does it really?

My knowledge around parsers is a bit limited, but I don’t see an issue with the following:

type throws = Error;

type SomeType = (): throws throws throws;

It’s a function that [type] (throws [type]) returns an error or throws an error.

The only issue I see right now is around compilation error hints, like I mentioned before. But that doesn’t necessarily mean the new keyword needs to be reserved, we only need to define proper precedence rules.

Although it would probably be better if there’s no ambiguity in general.

I think this would be a very monumental addition to the language and fix one of the biggest flaws of Javascript. It would enable typescript to be used in more use cases where comprehensive exception handling is a necessity. IMO it’s the current most important deficiency to address in the language.

Completely agree. Is there a good reason not to merge this?

@moshest Well, I prefer a better feature that takes longer than a partial solution that will take less time but we’ll stay stuck with it forever.

Can we follow the error declaration pattern of an OOP language like Java? “throws” keyword is used to declare that we need to “try…catch” when using a function with a potential error

Just my two cents, I’m converting code to async/await and so I’m being subjected to throw (as an aside, I hate exceptions). Having a throws clause as this issue discusses would be nice in my opinion. I think it would be useful too even if “throws any” was allowed. (Also, maybe a “nothrows” and compiler option that defaults things to “nothrows”.)

It seems like a natural extension to allow typing what a function throws. Return values can be optionally typed in TS, and to me it feels like throw is just another type of return (as evidenced by all the alternatives suggested to avoid throw, like https://stackoverflow.com/a/39209039/162530).

(Personally, I’d also love to have the (optional) compiler option to enforce that any caller to a function declared as throws must either also declare as throws or catch them.)

My current use case: I don’t want to convert my entire [Angular] code base to use exceptions for error handling (seeing as I hate them). I use async/await in the implementation details of my APIs, but convert throw to normal Promises/Observables when an API returns. It would be nice to have the compiler check that I’m catching the right things (or catching them at all, ideally).

It will work, but only in “specific environments”, which are becoming the majority as ES6 is being adapted.

@nitzantomer

Here’s an example: I have a web client in which the user can write/paste json data, then click a button. The app takes this input and passes it to a 3rd party library that somehow parses this json and returns the json along with the different types of the values (string, number, boolean, array, etc). If this 3rd party library throws a SyntaxError I can recover: inform the user that his input is invalid and he should try again.

This is certainly one area where the whole idea of checked exceptions becomes murky. It is also where the definition of exceptional situation becomes unclear. The program in your example would be an argument for JSON.parse being declared as throwing a checked exception. But what if the program is an HTTP client and is calling JSON.parse based on the value of a header attached to an HTTP response that happens to contain an ill-formed body? There is nothing meaningful the program can do to recover, all it can do is rethrow. I would say that this is an argument against JSON.parse being declared as checked.

It all depends on the use case.

I understand that you are proposing that this be under a flag but let’s imagine that I want to use this feature so I have enabled the flag. Depending on what kind of program I am writing, it may either help or hinder me.

Even the classic java.io.FileNotFoundException is an example of this. It is checked but can the program recover? It really depends on what the missing file means to the caller, not the callee.

Thanks for your input @kvenn!

The reason I wanted to introduce “safe” exceptions is to address an important note mentioned earlier:

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.

But perhaps it would be more dangerous and confusing than helpful. Some configuration to whitelist certain functions might be appropriate instead.

Regarding an RFC, for something like this I wouldn’t hesitate, but I have some ideas that I need to make sure are even feasible before I potentially waste others’ time. Regardless I would love a community effort, as it will be required to move the needle in any meaningful direction.

@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

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

@thw0rted you are requiring error annotation, or, try-catch. So you either handle the error or pass it up the chain for another method to handle. Ultimately, the build system will ensure that “Uncaught Exception” never happens if you are forced to handle all errors since all errors will ultimately end up in a try-catch that you would be forced to implement.

I think this can be very useful for stateful production apps that need stability and want to ensure that errors don‘t crash the entire app and are rather handled in a smarter way. I personally would use this on every project.

Default should be that every function throws unknown until said otherwise (throws never or else …) This way, there is no need to type every single lib function from the start.

Resulting throws type accessed in try-catch block is therefore everytime union of unknown and other types. Knowing that result is always union of unknown should simplify implementation, where TS actually care only about specified types and combines all of them inside union.

At least this logic seems to me easy to implement and does not require error types inside libs from start. Promise could include second generic param defaulting to unknown (backwards compatible).

In https://github.com/microsoft/TypeScript/issues/53031#issuecomment-1453897743 you said

in order for those to work, you have to specify how higher-order functions handle throws from their functional arguments

You don’t “have to” specify throws-behavior of every higher-order function right away. There’s no reason to assume that any function (higher-order or not) cares about the throws-type of a function that it takes as a parameter, unless told otherwise.

I do think that it would be important to support generic parameters in a throws position, which is suggested many times through the comments on this issue. I don’t see why that would make it impractical to deliver an MVP.

I don’t think that throws has any reason to rely on RTTI, as with any good TS feature it would operate entirely in type-space and erase away at runtime. That would only be necessary if TS wanted to guarantee that throws information is always 100% accurate, which is an explicit non-goal here. We just want to enable the type checker to identify certain specific cases where expectations about throwing behavior would be invalid. For example, it would be trivial to type JSON.parse as throws SyntaxError, then the compiler could flag function f(s: string) throws never { return JSON.parse(s); } as inaccurate.

@einarpersson in the language with exception you cannot have this ever, even if your whole project will use Error as values, your dependencies will not, and even if they will, then language itself can just throw something on you.

Yeah I understand it is not feasible 😃 It was more an expression of “wishful thinking” than anything else. That said, we do see some popular libs such as zod offering this approach as an option for those who prefer it

@einarpersson in the language with exception you cannot have this ever, even if your whole project will use Error as values, your dependencies will not, and even if they will, then language itself can just throw something on you.

I’d argue that having throws has value even if none of the existing libs or core libs had any throws annotations on them at all. I know that some will confuse it with “oh this has no throws type associated so there’s no way it will throw an error”. When the typing is wrong however you could deal with it in one of the following ways.

(non exclusive list)

  • Create an issue with the types
  • Fix the types yourself
  • Complain on your blog about how it’s not right
  • Just wrap the method in a try/catch now that you know
  • Annotate the method that calls that function with a throws

I don’t think the throws should be held to some sort of guarantee of “this will execute without failure”. Java’s checked exceptions definitely aren’t that.

I’d propose that a throws means “I have coded it and I know an exception can occur and I’m either just letting happen, I’m passing it up, or I’m actually the originator of the exception.”

And missing a throws means, “I don’t know of any myself”, or “I haven’t bothered to check”, rather that some sort of expectation that it can’t throw.

I don’t use my types as a strict 100% guarantee that no library creator is going to be lying about types, just that my code isn’t lying cause I didn’t write any code that’s lying.

I don’t see why throws would be considered any other way, especially after it first comes out.

Worst case, we could introduce !throws, if you really need to express, “I’ve done my research and there is no way this method can throw!”

@Xample Thanks for your feedback, but I did not mean the technical implications that we are currently discussing. My point was more, that I do not understand the “process” of language features being selected to be part of the specification. Is there any transparency? Or is it (at the end) business- or company-driven? Are this suggestions “heard” by the TS team?

To be honest, I totally understand your thoughts of add this to the language or to leave it out. There is always a downside that needs to be discussed. But after six years there could have been at least a “signal” like:

  • “we are thinking about this…”
  • “this cannot be done because…”
  • “cool new feature, but…”
  • “we will implement this in version x.y…”
  • “we do not have the capacity to implement this, but feel free to open a PR…”
  • “this will not be implemented!” —> CLOSED

This is the expected way. Everything else is like ghosting.

Sorry @jimisaacs I had to read you comment a couple of times and I couldn’t understand it. So you are saying that we should have this ?

async function myFunc2(arg: any): 'async return':

That doesn’t and should not compile, with sugar syntax or not. The typing for the return value of a function should be what the function returns. When a functions is marked as async, it will always return a promise or reject, even if it throws. So we can just make this fail to compile:

async function myFunc2(arg: any): throws Error : Promise<string>

The same way typing an async function with a non promise return type fails. Something like

The throw type of an async function or method must be the global Promise<T, R> type. Did you mean to write 'Promise<string, Error>'?

The real problem is when you do await on a non async functions that returns a promise but can throw synchronously:

const func = (param: boolean) throws Error : Promise<string> => {
  if(param) throw new Error("sync error");
  return Promise.resolve("sync result");
}

await func();

The typing for func is throws Error : Promise<string>, but when using await it’s becomes something similar to : Promise<string, Error>. So we’d need a way for typescript to do something like this type conversion under the hood:

type syncToAsync<F extends (...args: any) throws any => any> =
  F extends await (...args: infer Args) throws infer Throw  => infer Return
    ? (...args: Args) => Promise<Return, Throw>
    : F;

@Ranguna @owl-from-hogvarts yes, original Promise typing is not very good. we need A Promise typing with 2 generic-parameter like this as following

abstract class Promise<V, E = unknown> {
    abstract constructor(
        fn: (resolver: (v: V) => void, rejector: (e: E) => void) => void
    );

    abstract static resolve<V>(v: V): Promise<V, never>;
    abstract static reject<E>(e: E): Promise<never, E>;

    abstract then<NV, NE>(fn: (v: V)  => Promise<NV, NE>): Promise<NV, E | NE>;
    abstract catch<NV, NE>(fn: (e: E) => Promise<NV, NE>): Promise<NV, NE>;
}

and the getJson should be

const getJson = (url) throws 'sync err' : Promise<never, 'async error'> => {
  // something bad happened and                                        ^^^^^^^^^
  if (Math.random() > 0.5) throw 'sync err';

  return Promise.reject("async error"); // or a more complex promise with a possible rejection 
}

it imply a fact that the Promise rejection is just a value.

now ts fn types constructed by a tuple in 3 parts: this type args types and return type as follow:

// and fn can be constructed by T A R
type Fn<T, A extends unknown[], R> = (this: T, ...args: A) => R;

// and they are all inferable:
type FnParts<Fn> = (
    Fn extends (this: infer T, ...args: infer A) => infer R
        ? [T, A, R]
        : never
)

if we need a typed error handling, we need to invole error into this three parts and obviously R is the best part the typed error should be placed to as follow:

type Err<E, R> = { err: true, value: E } | { err: false, value: R };
type Fn<T, A extends unknown[], R extends Error<any, any>> = (this: T, ...args: A) => R;

and a fn may cause error will be something like this:

enum IncErr {
    OutOfRange = 'OutOfRange',
}

export function tryInc(x: number): Err<IncErr, number> {
    if (x > 0) return { err: false, value: x + 1 };
    return { err: true, value: IncErr.OutOfRange };
}

// invoke three times for tryInc
export function tryIncIncInc(x: number) {
    const r1 = tryInc(x);
    if (r1.err) return r1;

    const r2 = tryInc(r1.value);
    if (r2.err) return r2;

    return tryInc(r2.value);
}

// but we just want:
export function tryIncIncIncV2(x: number) {
    return tryInc(tryInc(tryInc(x)));
}

obvisously, if we put the error types mixed into return type, we will got some nested error hell like Err<1, Err<2, Err<3, 4>>, and we have to denested it by many if statement.

so, i think the error type should not be mixed with R, it should be a new forth parts of a fn type:

type Fn<T, A extends unknown[], E, R> = (this: T, ...args: A) throws E => R;
// or
type Fn<T, A extends unknown[], E, R> = (this: T, ...args: A) >> E => R;

// and all of parts are inferable:
type FnParts<Fn> = (
    Fn extends (this: infer T, ...args: infer A) >> infer E => infer R
        ? [T, A, R, E]
        : never
);

and tryInc and tryIncIncInc will be

export function tryInc(x: number) >> IncErr : number {
    if (x > 0) throw IncErr.OutOfRange;
    return x + 1;
}

export function tryIncIncInc(x: number) >> IncErr : number {
    return tryInc(tryInc(tryInc(x)));
}

// and tsc can duduce the error types for a tryIncIncIncV3:
export function tryIncIncInc(x: number): number {
    return tryInc(tryInc(tryInc(x)));
}

// and the error handling:
export function tryIncIncInc(x: number): number {
    try {
        return tryInc(tryInc(tryInc(x)));
    } catch (e) { // typeof e should be (IncErr | IncErr | IncErr) deduced from `tryInc(tryInc(tryInc(x)))`
        return 0;
    }
}

// or a new tsconfig flag --strictErrorHandling for a strict error checking
export function tryIncIncInc(x: number): number {
    return tryInc(tryInc(tryInc(x))); // error here: thrown error should be caught by `try {} catch (e) {}`
}

// or `<< sugar`
type ErrorTypeOf<Fn> = FnParts<Fn>[2]; // [T, A, E, R][2]
function tryIncIncInc(x: number) >> ErrorTypeOf<typeof tryInc> : number;
function tryIncIncInc(x: number) << tryInc : number;

and a none error fn’s Error Type will be never:

const safeInc = (x: number) => x + 1;
type F = FnParts<typeof safeInc>; // F is [unknown, [x: number], never, number]
function test() {
    try {
        safeInc(safeInc(1))
    } catch (e) { // so, e here should be `never | never`
    }
}

async function and error handling:

enum HttpErr { E404 = 404, E500 = 500, };
declare async function getJson(url: string) >> HttpErr : Promise<string>;

async function printJson(url: string) {
    try {
        const json = await getJson(url);
        console.log('url ===>', json);
    } catch (e) {
        if (e === HttpErr.E404) console.log('404 when getting', url)
    }
}

@allicanseenow The proposal is for optional usage of the throws clause. That is, you won’t have to use try/catch if you’re using a function that throws.

For those that are looking for compile-time error safety in typescript you can use my ts-results library.

Aside from errors, is it also possible to mark other kinds of side-effects like

  • Divergence (infinite loop / not returning)
  • IO
  • Non-determinism (Math.random)

It reminds me Koka from MSR which can tag effects on returning types.

The proposal:

function square(x: number): number &! never { // This function is pure
  return x * x
}

function square2(x : number) : number &! IO {
  console.log("a not so secret side-effect")
  return x * x
}

function square3( x : number ) : number &! Divergence {
  square3(x)
  return x * x
}

function square4( x : number ) : number &! Throws<string> { // Or maybe a simple `number &! Throws`?
  throw "oops"
  return x * x
}

function map<T, R, E>(a: T[], f :(item: T) => R &! E): R[] &! E { ... }
function map<T, R>(a: T[], f :(item: T) => R): R[] { ... } // will also work; TS would collect side effects

function str<T>(x: T): string &! (T.toString.!) // care about the side effect of a type

Problems addressed in the example above:

  • its not restricted to errors, you can reject with anything (although probably a bad idea)
  • its not restricted to using instanceof, you can use any predicate type guard to remove errors from the union

The main issue I see using this for all code are callback signatures, to make it work in a compatible way the default “return type” would be “might-throw-any”, and if you want to restrict that you would say throws X or throws never if it doesn’t e.g.

declare function pureMap<T>(t:T[], f: (x:T) => U throws never):U[] throws never

The version without the signature:

declare function dirtyMap<T>(t:T[], f: (x:T) => U):U[]

would actually default to

declare function dirtyMap<T>(t:T[], f: (x:T) => U throws any):U[] throws any

to guarantee that all current code compiles.

Unlike with strictNullChecks, where null return values are quite uncommon, I think exceptions in JS are quite pervasive. Modelling them in .d.ts files might not be too bad (import types from dependencies to describe your errors), but it will definitely be a non trivial effort and will result with huge unions.

I think a good middle ground would be to focus on Promises and async/await, as a Promise is already a wrapper, and async code is where error handling branches the most in typical scenarios. Other errors would be “unchecked”

@nitzantomer: Do you think it’s possible to implement a TSLint check which informs the developer when a function with @throws is called without a surrounding try/catch?

@aluanhaddad I’m aware of that, which is why I offered a way to accomplish what @grofit requested but without the need to extend Error. A developer will still be able to extend Error if he wants to, but the compiler will be able to generate js code which doesn’t need to use instanceof

look you don’t seem to get what i am saying, we are going in circles

by letting SyntaxError thrown from 3rd party library you are exposing your user to the implementation details of your own code which are supposed to be encapsulated

basically you are saying, hey, it’s not my code that doesn’t work, it’s that stupid library that i found on the internet and used, so if you have a problem with it, deal with that 3rd party lib, not me, i just said what i was asked to

and there is no guarantee that you can still use the instance of that 3rd lib after that SyntaxError, it’s your responsibility to provide guarantees to the user, say by reinstantiating the 3rd party control after it threw

bottom line, you need to be in charge for handling inner exceptions (not all of them, only the checked ones, i beg you)

@HolgerJeromin Why would it be needed?

Constructing a RegExp from non-hardcoded input is so vanishingly rare that it seems obnoxious to force a try/catch

It would be simpler to just use // @ts-expect-error than introducing a whole new error handling concept.

I think it’s important enough to not be closed and encourage more discussion

Personally I agree. That being said, as @kvenn stated, the TS team has already made their stance clear. Robust error handling needs to be normalized in the community before such an invasive new concept is added to countless others’ workflows. If our ideas aren’t able to proliferate, well, perhaps they were doomed to begin with.

why is this issue closed? Is there any definitive conclusion?

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…

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.

[^1]: Which is perhaps unfortunate; the implications of untagged union and intersection types are very interesting from a type-theoretical perspective. But I digress.

The TypeScript team refuses to implement this feature for one reason: it’s impossible to detect the throw type of a function that doesn’t use the throws keyword. In JavaScript/TypeScript, errors/exceptions are bubbled from the deepest level to the top level, implementing throws syntax for new functions will not make sense, unless you try-catch every function call in your code and make sure unintended errors/exceptions are properly dealt with, which is impossible.

However, we could use type assertions to make sure calling a history function will always result in one or several intended errors/exceptions if occurred, examine this code:

// new function explicitly throws a pre-defined error type
function doSomethingWith(tool: string): boolean throws TypeError {
    if (typeof tool !== "string" || tool === "" || tool === null || tool === undefined) {
        throw new TypeError('argument 'tool' must be a string and must not be empty');
    }   

    // old function is asserted to throw a particular error type.
    const requirements = doAnotherThing() throws ReferenceError;
    return requirements.isOk;
}

try {
   // calling the function will either return boolean, or throwsTypeError | RefrerenceError
    const isProperlyDone = doSomethingWith("hammer");
} catch (e: TypeError) {

} catch (e: ReferenceError) {

} catch (e: never) {
    // this block is just for demonstration, it shall never be hit since now we can
    // infer that doSomethingWith() will only throws TypeError or ReferenceError.
}

Ah…I know that. My mistake to understand that as there is workaround to get sync/async function error union type AErr|BErr|CErr

@factoidforrest

typescript doesn’t even realize that if I throw the function will terminate, it still asks me to return something.

Hummm… It does not?

This does not throw

const a = (b: boolean) => {
  if (b) return "something";

  throw new Error();
}

But it throws if you comment out the throw.

Edit: this is wrong

What is the rationale behind typescript totally ignoring that Errors exist in JS? Even for flow control, typescript doesn’t even realize that if I throw the function will terminate, it still asks me to return something. Granted, just because a function throws, it doesn’t mean thats the ONLY thing it could ever throw, but now that typescript is so pervasive in the ecosystem, we could actually have a somewhat complete set of annotations for what functions could throw and what they throw.

It would be extremely helpful to be able to see in my IDE that a function has a good chance of throwing something.

Why does TS just totally ignore errors being part of the language? It’s like the one part of the system that still has no typing of any kind, a parallel path of untyped mayhem. I realize that yes, there are a lot of edge cases that make it hard to know exactly what errors are going to be thrown, but FFS, let’s at least make an attempt to provide some useful information to the developer.

Despite all your good ideas and suggestions there is one thing left I really don’t understand.

The community states a need for an enhancement of the language. Is is discussed for nearly 6 (!) years and there is no progress. Is there anyone keeping an eye on those suggestions from Microsoft side? Is there a defined process of how new features are being selected like for JavaScript (TC39)? From the outside there is simply no progress since six years. Neither positive nor negative, which is quiet frustrating. Even a feedback that you will not work on this would help. Our just a glimpse of a reaction.

@DanielRosenwasser or @RyanCavanaugh or simply anyone else of the TS team… Could someone enlighten us? Please…?

@thw0rted Yes there is that possibility of a typo, but if a function declaration, it would result in a function with either no definition, or a double definition (depending on which way the typo goes), which is definitely going to cause a compile error.

In general, I feel the possibility of a typo being valid syntax is okay as long as it’s trivially detectable

throws would definitely have to be a reserved word. Also: I liked the idea of throws, but it occurred to me that typos could be a problem. Since semicolons aren’t required to separate statements,

type MyCallback = (x: number)
  => void
  throw Error;

would be perfectly valid TS. Of course, what it would actually do is define a type then throw the Error constructor, but it isn’t invalid syntax – though it is a good argument for getting in the habit of using semicolons consistently.

So, between the fact that it’s a breaking change and the typo issue, maybe it would be better to reuse an existing keyword or symbol?

Agreed with ‘throws’ over symbols.

throws also allows for an implicit function return with an explicit throws.

That would be especially useful as libraries transition their types so in your own first party functions you can simply add the throws clause while retaining implicit return typing.

function f(): throws Error {
  return someErrorProneFunction()
}

Using the /symbol would be pretty awkward in that scenario

function f(): / Error {
  return someErrorProneFunction()
}

@starikcetin That makes me more so think of https://github.com/microsoft/TypeScript/issues/8655, not this. Pretty sure that’s orthogonal to this.

I prefer to always throw Error object :

function fn(num: number): void {
    if (num === 0) {
        throw new TypeError("Can't deal with 0")
    }
}
try {
 fn(0)
}
catch (err) {
  if (err instanceof TypeError) {
   if (err.message.includes('with 0')) { .....}
  }
}

Another approach if you want type safety is to just treat errors as values a la Golang: https://gist.github.com/brandonkal/06c4a9c630369979c6038fa363ec6c83 Still, this would be a nice feature to have.

@nitzantomer I support the idea of adding throws to TypeScript, but wouldn’t an optional try/catch allow for potentially inaccurate throws declarations when used in nested functions?

For a developer to trust that a method’s throws clause is accurate, they would have to make the dangerous assumption that at no point in that method’s call hierarchy was an optional exception ignored and thrown up the stack unreported. I think @ajxs may have alluded to it at the end of his comment, but I think this would be a large issue. Especially with how fragmented most npm libraries are.

I love the idea of declaration of error type! This would a big help for people who need it and won’t do anything bad for people who don’t like it. I have now the problem in my node project, which could be solved more faster with this feature. I have to catch errors and send proper http code back - to do that I have to always check all functions calls to find out exceptions I have to handle - and this is no fun -_-

PS. The syntax in [https://github.com/Microsoft/TypeScript/issues/13219#issuecomment-428696412](previous comment) looks very good - &! and wrapping error types in <> just amazing.

@bluelovers no, typescript doesn’t currently support the throws keyword, this is what this issue is all about.

@aleksey-bykov Let’s not change the nature of JavaScript, let’s just adding typing to it. 😃

@aluanhaddad For what @grofit asks for you can then do something like:

try {
    // do something
} catch (isNotFoundError(error)) {
    ...
}  catch (isSomeBadDataError(error)) {
    ...
} catch (error) {
    ...
}

Where the isXXX(error) are type guards of the form of: function isXXX(error): error is XXX { ... }

Oh, I see. It will drill down through all methods I call and collect all possible exception types? I would love to see it happen, if it is possible. See, a developer can always have an unexpected exception that won’t be declared, in which case an “object not set to instance” or “divide by 0” or similar exceptions are always possible almost from any function. IMHO, it would have been best handled like in C# where all exceptions inherit from a base class that has a message and not to allow at all the throw unwrapped text or other objects. If you have base class and inheritance you can cascade your catches and handle your expected error in one block, and other unexpected in another.

@aleksey-bykov Fair enough, name changed.