Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you specify conditional return value types in TypeScript?

Tags:

typescript

I have written the following function to help with error handling

const capture = <T>(
  callback: () => T
): { result?: T; error?: Error } => {
  try {
    return { result: callback(), error: undefined };
  } catch (err) {
    return { result: undefined, error: err as Error };
  }
};

Example usage:

const { result, error } = capture<number>(() => foo());
if (error) badPath();

console.log("hooray we got a result", result)

However the TypeScript compiler will complain:

Object is possibly 'undefined'. ts(2532)
const result: number | undefined

I understand why the compiler is complaining (this is expected behaviour for using optional params).

However I was wondering if there existed some TypeScript shenanigans that could support conditional return types.

i.e. is there a way we could specify capture's signature such that when error doesn't exist, result is inferred to exist? And vice versa?

like image 901
utpamas Avatar asked Dec 05 '21 21:12

utpamas


2 Answers

  • Define the return value of your capture function as { result: T; error: undefined; } | { result: undefined; error: Error; }.
  • Do not destructure the return type; check its properties instead. TypeScript is not smart enough yet to link the types of distinct variables in this case.
const capture = function<T>(
  callback: () => T
): { result: T; error: undefined; } | { result: undefined; error: Error; } {
  try {
    return { result: callback(), error: undefined };
  } catch (err) {
    return { result: undefined, error: err as Error };
  }
}

const retVal = capture<number>(() => foo());
if (retVal.error)
    badPath();
else
    console.log("hooray we got a result", retVal.result);

See this playground link.

For a more robust implementation, we need to take into account the case that the error thrown is not an instance of Error. In this case, checking the truthness of the error property in the result is not sufficient. We could here, for example, switch to test for the existence of the error property itself. To do that, let's change the return type to just { result: T; } | { error: unknown; }. An implementation would then look like:

const capture = function<T>(
  callback: () => T
): { result: T; } | { error: unknown; } {
  try {
    return { result: callback() };
  } catch (err) {
    return { error: err };
  }
}

const retVal = capture<number>(() => foo());
if ('error' in retVal)
    badPath();
else
    console.log("hooray we got a result", retVal.result);

And the playground link.

like image 162
GOTO 0 Avatar answered Nov 03 '22 00:11

GOTO 0


That's not possible because TypeScript doesn't know about runtime.

The closest thing that comes to mind is to allow T to be undefined

(callback: () => T | undefined)

and then filter it along with error, making the rest of your code understand that result at that point is a number and can't be undefined.

result type here in number

const capture = <T>(callback: () => T | undefined): { result?: T; error?: Error } => {
  try {
    return { result: callback(), error: undefined }
  } catch (err) {
    return { result: undefined, error: err as Error }
  }
}

const start = () => {
  const { result, error } = capture<number>(() => foo())
  if (error || !result) {
    // something
    return
  }

  // result // here it will be `number`
  // (cannot be undefined because we checked)

  console.log('hooray we got a result', result)
}
like image 38
BrunoLM Avatar answered Nov 02 '22 23:11

BrunoLM