160

In Java I would declare a function like this:

public boolean Test(boolean test) throws Exception {
  if (test == true)
    return false;
  throw new Exception();
}

And I can use this function without handling the exception.

If it is possible, how to do the same in Typescript? The compiler will tell me that I can't use the function without a try/catch.

JJJ
  • 32,902
  • 20
  • 89
  • 102
marcXandre
  • 2,072
  • 2
  • 13
  • 21
  • 1
  • Not sure if you are looking for `Error` class, check my answer below – Niladri Mar 22 '18 at 17:33
  • You could annotate it via decorators to indicate that the method may throw, but there's nothing that can enforce that. You would have to inspect the annotation to check every time you called that method. – Jeff Mercado Mar 22 '18 at 20:43
  • Not 100% sure what your goal is here -- if you return a boolean type, you're done. There's nothing you need to do to "use this function without handling the exception", I don't believe. Just as a for instance, I wanted to have a function that wrapped throwing an error (that is, the function would throw _every time_, versus your "half the outcomes"), and eventually decided on marking it as if it returned a specific type that matched the caller's other outcome. `const x = success ? handleX(x) : handleError(err)` where `handleX` and `handleError` "return" the same type. – ruffin Aug 01 '23 at 19:56

9 Answers9

162

There is no such feature in TypeScript. It's possible to specify error type only if a function returns an error, not throws it (this rarely happens and is prone to be antipattern).

The only relevant type is never. It is applicable only if a function definitely throws an error, it cannot be more specific than that. It's a type as any other, it won't cause type error as long as it doesn't cause type problems:

function Test(): never => {
  throw new Error();
}

Test(); // won't cause type error
let test: boolean = Test(); // will cause type error

When there is a possibility for a function to return a value, never is absorbed by return type.

It's possible to specify it in function signature, but for reference only:

function Test(test: boolean): boolean | never {
  if (test === true)
    return false;

  throw new Error();
}

It can give a hint to a developer that unhandled error is possible (in case when this is unclear from function body), but this doesn't affect type checks and cannot force try..catch; the type of this function is considered (test: boolean) => boolean by typing system.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 37
    “`never` is omitted by typing system in favour of return type” … This is technically incorrect. The reason you don’t have to specify `never` in a return type is that in a union, every other type *absorbs* it. So `boolean|never` is simplified to `boolean`, like how `anything || false` simplifies to `anything`: in a disjunction, anything *absorbs* `false`. In a way, `never` is the opposite of [`unknown`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type), which *dominates* in a union, and is *absorbed* in an intersection. – chharvey Nov 29 '18 at 17:18
  • 1
    Thanks for the notice. Indeed, adsorption is the correct term here. – Estus Flask Nov 29 '18 at 17:31
  • 2
    @chharvey Just a quick note: `anything || false` does not simplify to `anything` (unless you meant for `anything` to represent all truthy values, but that was unclear). If the left side of `||` is falsy, JavaScript takes the right side regardless of its value. Try `null || false` in your console and you'll see you get `false`. – willurd Aug 23 '20 at 16:33
  • 1
    @willurd yes it’s a minor technicality but you are right. i should have said `anything_truthy || false` simplifies to `anything_truthy`. thanks for the correction. the difference is that `anything_truthy || false` is an expression that *short-circuits* at runtime, whereas `boolean | never` is a type that statically simplifies. – chharvey Aug 27 '20 at 04:49
  • @chharvey Yeah I figured that's what you meant. I just think it's important to be precise no matter how pedantic, especially for newer people that may be learning e.g. the intricacies of JavaScript. – willurd Aug 28 '20 at 05:07
  • Good point willurd :+1: - pedantic, accurate however you want to name it :-) – Kieran Ryan Oct 08 '20 at 16:37
  • 2
    NIce, thanks for the `never` solution, it muted the compiler error forcing us to add `undefined` to the return type if we have a wrapper error handling function with `void`, which returns undefined by TS def. Basically `void vs never`. – LeOn - Han Li Jun 17 '21 at 16:12
  • @LeOn-HanLi Sounds interesting! Could you elaborate? Do you perhaps have a TypeScript Playground of your code? – Andru Nov 16 '21 at 11:27
31

You can mark the function with @throws jsdoc at least. Even though it does not provide static analysis errors in typescript compiler, some good IDE or linter may still report a warning if you try to disregard the function that throws...

/** 
 * @throws {Error}
 */
function someFunc() {
    if (Math.random() < 0.5) throw Error();
}
someFunc();

enter image description here

Klesun
  • 12,280
  • 5
  • 59
  • 52
  • 1
    By your knowledge, is there any way to enable such a warning on Visual Studio Code? That would be tremendously helpful. – Jacopo Tedeschi Jan 12 '22 at 21:02
  • 7
    I checked eslint-plugin-jsdoc, but my understanding is that the require-throws rule only enforces exceptions to be documented in JSDoc. Sadly it does not enforce try-catch statements inside the code that makes use of the function, which is what many of us are looking for. Any alternatives? – Jacopo Tedeschi Jan 12 '22 at 21:13
23

It is not possible at this moment. You can check out this requested feature: https://github.com/microsoft/TypeScript/issues/13219

backus
  • 4,076
  • 5
  • 28
  • 30
sompnd
  • 561
  • 5
  • 7
2

You could treat JavaScript's Error as Java's RuntimeException (unchecked exception). You can extend JavaScript's Error but you have to use Object.setPrototypeOf to restore the prototype chain because Error breaks it. The need for setPrototypeOf is explained in this answer too.

export class AppError extends Error {
    code: string;

    constructor(message?: string, code?: string) {
        super(message);  // 'Error' breaks prototype chain here
        Object.setPrototypeOf(this, new.target.prototype);  // restore prototype chain
        this.name = 'AppError';
        this.code = code;
    }
}

lmiguelmh
  • 3,074
  • 1
  • 37
  • 53
  • 1
    This prototype change is not required for ES2015 and above, where inheriting from Error works as expected. If your transpilation targets ES2015+, you don't need to worry about it! – Mack Aug 03 '23 at 02:41
2

As indicated in other answers, in typescript, the return type of a fallible operation is never. There is no way to mark a function as throws however you can use a utility type to make it more discernible:

type Result<OK = any> = OK | never;

Or you can make it even more noticeable:

type Result<OK = any, Error = never> = OK | Error;

Again, these are for the eyes only, no way to enforce try/catch block.

If you want to force handling of errors, use a promise. Linters can cath unhandled promises. "typescript-eslint" has "No floating promises" rule.

https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-floating-promises.md

Also some runtimes emit errors when there is an unhandled promise.

snnsnn
  • 10,486
  • 4
  • 39
  • 44
1

You cannot using pure ts (v<3.9) I hope it will be available in the future. A workaround is however possible, it consists of hiding the possible thrown types in the method's signature to then recover those types in the catch block. I made a package with this workaround here: https://www.npmjs.com/package/ts-throwable/v/latest

usage is more or less as follow:

import { throwable, getTypedError } from 'ts-throwable';
class CustomError extends Error { /*...*/ }

function brokenMethod(): number & throwable<CustomError> {
    if (Math.random() < 0.5) { return 42 };
    throw new CustomError("Boom!");
}

try {
    const answer: number = brokenMethod()
}
catch(error){
    // `typedError` is now an alias of `error` and typed as `CustomError` 
    const typedError = getTypedError(error, brokenMethod);
}

Flavien Volken
  • 19,196
  • 12
  • 100
  • 133
1

This seems like a interesting PR to follow regarding this topic https://github.com/microsoft/TypeScript/pull/40468

This PR introduces:

  • A new type-level expression: throw type_expr. Currently throw type only throws when it is being instantiated.
  • A new intrinsic type TypeToString to print a type
Nico
  • 872
  • 10
  • 16
1

Coming from a functional background, I prefer to specify expected errors (aka checked exceptions) in the return type. Typescript unions and type guards make this simple:

class ValidationError {
  constructor(readonly message: string) {}

  static isInstance(err: unknown): err is ValidationError {
    if (err === undefined) return false
    if (typeof err !== 'object') return false
    if (err === null) return false
    return err instanceof ValidationError
  }
}

function toInt(num: string): number | ValidationError {
  const result = Number.parseInt(num)
  if (result === undefined) return new ValidationError(`Invalid integer ${num}`)
  return result
}

// caller
const result = toInt("a")
if (ValidationError.isInstance(result))
  console.log(result.message)
else
  console.log(`Success ${result}`)

This way, the function signature highlights the potential error to other developers. More importantly the IDE & transpiler will force developers to deal with them (in most cases). For example this will fail:

const result = toInt("a")
const doubled = result * 2

error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type
Toby Hobson
  • 199
  • 7
0

Not TypeScript, but Hegel might be of interest which is another type-checker for JavaScript, and has this feature. You'd write:

function Test(test: boolean): boolean | $Throws<Exception> {
  if (test)
    return false;
  throw new Exception();
}

See https://hegel.js.org/docs/magic-types#throwserrortype

P Varga
  • 19,174
  • 12
  • 70
  • 108