1

Here I have a function that works well for catching sync errors, and doing something with them before re-throwing them.

    function logExceptions<T extends (...args: any[]) => any>(func: T): (...funcArgs: Parameters<T>) => ReturnType<T> {
      return (...args: Parameters<T>): ReturnType<T> => {
        try {
          return func(...args);
        } catch (err) {
          console.log(func.name + " caused an error")
          throw err;
        }
      };
    }

function syncExample() { 
  throw new Error()
}

logExceptions(syncExample)();

console.log

"syncExample caused an error"

Can I rewrite this function to become async agnostic and also work for async functions?

async function asyncExample() { 
  throw new Error()
}
logExceptions(asyncExample)();

desired console.log

"asyncExample caused an error"

actual console.log

""

user1283776
  • 19,640
  • 49
  • 136
  • 276
  • 1
    "*Can I rewrite this function to become async agnostic and also work for async functions?*" - no. You should write a separate function for that. – Bergi Jun 11 '20 at 13:54

3 Answers3

4

Can I rewrite this function to become async agnostic and also work for async functions?

No. While you could try to overload it and detect whether the function would return a promise or not, that's very fragile. Better write a separate function for wrapping asynchronous functions:

function logExceptions<T extends any[], U>(func: (...args: T) => PromiseLike<U>): (...args: T) => Promise<U> {
  return async (...args) => {
    try {
      return await func(...args);
    } catch (err) {
      console.log(func.name + " caused an error")
      throw err;
    }
  };
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

Agree with @Bergi about the new function.


Typescript doesn't like it when we return directly the ReturnType in case of async method use. I guess it's because I havent specified that ReturnType must be of type Promise, but I've found now way how to specify it.

type ReturnType any> = T extends (...args: any) => infer R ? R : any Obtain the return type of a function type

The return type of an async function or method must be the global Promise type.(1064)

I've found a turnaround by extracting what's templated inside of the Promise and redeclaring it.

type ExtractPromiseTemplate<T> = T extends PromiseLike<infer U> ? U : T

function logExceptions<T extends (...args: any[]) => ReturnType<T>>(func: T): (...funcArgs: Parameters<T>) => Promise<ExtractPromiseTemplate<ReturnType<T>>> {
      return async (...args: Parameters<T>): Promise<ExtractPromiseTemplate<ReturnType<T>>> => {
        try {
          console.log('Will call now');
          const ret = await func(...args);

          return ret as ExtractPromiseTemplate<ReturnType<T>>;
        } catch (err) {
          console.log(func.name + " caused an error");

          throw err;
        }
      };
    }

async function asyncExample() { 
  throw new Error('Example')
}

logExceptions(asyncExample)();

Call the following code to test the validity of the returned value :

type ExtractPromiseTemplate<T> = T extends PromiseLike<infer U> ? U : T

function logExceptions<T extends (...args: any[]) => ReturnType<T>>(func: T): (...funcArgs: Parameters<T>) => Promise<ExtractPromiseTemplate<ReturnType<T>>> {
      return async (...args: Parameters<T>): Promise<ExtractPromiseTemplate<ReturnType<T>>> => {
        try {
          console.log('Will call now');
          const ret = await func(...args);

          return ret as Promise<ExtractPromiseTemplate<ReturnType<T>>>;
        } catch (err) {
          console.log(func.name + " caused an error");

          throw err;
        }
      };
    }

async function asyncExample():Promise<string> { 
  return 'a';
}

(async() => {
  const ret = await logExceptions(asyncExample)();
})();


New playground for @bergi comment

Orelsanpls
  • 22,456
  • 6
  • 42
  • 69
  • That looks pretty great. Would it be possible to simplify the solution and maybe avoid the ExtractPromiseTemplate if the function was rewritten to only handle async functions? – user1283776 Jun 11 '20 at 14:20
  • 1
    This method already only reference async functions. The thing is, typescript is making an error when simply returning `ReturnType` that's why I used a workaround. If someone reading my answer could find an other way it would be great. I agree it is pretty big. – Orelsanpls Jun 11 '20 at 14:21
  • @GrégoryNEUT I think it should be `logExceptions Promise>` to make sure the passed-in function is asynchronous. You then could probably drop the `Promise>` and just use `ReturnType` for the return types. – Bergi Jun 11 '20 at 14:58
  • 1
    Also you shouldn't need to cast the `ret` to a different type, it should just work - and notice that `ret` is not a promise. – Bergi Jun 11 '20 at 15:01
  • @Bergi I've created a playground using `Promise`, the thing is, we lose the type of the returned parameter. `ret` will be of type `any`. Also you are right, I made a mistake on the returned value type that I'll edit right now. – Orelsanpls Jun 11 '20 at 15:22
  • Ah, I see. What I would've suggested causes "*Type `(...args: any[]) => Promise` is not assignable to type `T`. `(...args: any[]) => Promise` is assignable to the constraint of type `T`, but `T` could be instantiated with a different subtype of constraint `(...args: any[]) => Promise`.*" – Bergi Jun 11 '20 at 15:27
  • 1
    [This](https://www.typescriptlang.org/play/index.html#code/GYVwdgxgLglg9mABAGzgcwKIA8IFMAOsCAzgDwAqiuWUuYAJsYgBQB07AhgE5rEBciDmACeAbQC6ASkQBeAHyIAClzgBbGMVykhwuXOahIA8pOOIA3gChEN211xQQXJMw7FhkFu1bde0+RbWtsFQXMKBwZGIECRwyLisqGjMAOQA6jDIyNEcWYhgcADuKZIA3EFRNjFgxFCI9nUygoUcMHWGEGycPMRllhWVDU5IDeWViAC+OVAQABYsuFxc0lbjVbHxiegG4BCsYByquIgA1IgARDkgmvSCSIsqXOd9A1FQsyqFVEtjlROvk2kbkQ5F+Nn+ljcHggiA6RCQUMg2EO+HizFMyjUGi0tS4MDAaAU5kQQSGzkQKQ4KXKENc7kg6NkRKC1Vq9QcsmarTqSWweEI8BqdOhyNUqNwknRNMlZSAA) works but is ugly (and slightly incorrect) as it requires a cast. – Bergi Jun 11 '20 at 15:33
  • @Bergi you should post it as an answer. It's a valid alternative. Hope someone would find a better way thought – Orelsanpls Jun 11 '20 at 15:40
  • 1
    @GrégoryNEUT Posted an answer now that does it properly by just using two type parameters – Bergi Jun 11 '20 at 15:51
0

This can leverage Awaited

function logExceptions<T extends (...args: any[]) => any>(
  func: T
): (...funcArgs: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
  return async (...args: Parameters<T>) => {
    try {
      return await func(...args);
    } catch (err) {
      console.log(func.name + " caused an error");
      throw err;
    }
  };
}