Search code examples
node.jstypescripttypescript-decorator

TS decorator to wrap function definition in try catch


Is it possible to use TS decorator to wrap a function definition into a try-catch block. I don't want to use try-catch in every function so I was thinking maybe decorators can help. For example

function examleFn(errorWrapper: any) {
  try{
    // some code
  } catch (err) {
  errorWrapper(err)
  }
}

Something like this can be done in a decorator so that it can be used for other functions too.


Solution

  • No, you cannot decorate functions.

    TypeScript's implementation of decorators can only apply to classes, class methods, class accessors, class properties, or class method parameters. The relevant proposal for JavaScript decorators (at Stage 3 of the TC39 Process as of today, 2022-07-21) also does not allow for decorating functions.

    Function decorators are mentioned as possible extensions to the decorator proposal, but are not currently part of any proposal for either TypeScript or JavaScript.


    You can, of course, call a decorator-like function on another function, but this is just a higher-order function and not a decorator per se, and it won't affect the original function declaration:

    const makeErrorWrapper = <T,>(errorHandler: (err: any) => T) =>
        <A extends any[], R>(fn: (...a: A) => R) =>
            (...a: A): R | T => {
                try {
                    return fn(...a);
                } catch (err) {
                    return errorHandler(err);
                }
            };
    

    The makeErrorWrapper function takes an error handler and returns a new function that wraps other functions with that error handler:

    const errToUndefined = makeErrorWrapper(err => undefined);
    

    So now errToUndefined is a function wrapper. Let's say we have the following function which throws errors:

    function foo(x: string) {
        if (x.length > 3) throw new Error("THAT STRING IS TOO LONG");
        return x.length;
    }
    // function foo(x: string): number
    

    If you call it directly, you can get runtime errors:

    console.log(foo("abc")); // 3
    console.log(foo("abcde")); // 💥 THAT STRING IS TOO LONG
    

    Instead you can wrap it:

    const wrappedFoo = errToUndefined(foo);
    // const wrappedFoo: (x: string) => number | undefined
    

    Now wrappedFoo is a new function that behaves like foo and takes the same parameter list as foo, but returns number | undefined instead of just number:

    console.log(wrappedFoo("abc")) // 3
    console.log(wrappedFoo("abcde")) // undefined
    

    Playground link to code