Typescript type T or function () => T usage

Viewed 2791

You can see a demo in this playground.

I've made a simple generic type which can represent either a variable or a function that returns a variable. But, unfortunately, it doesn't work with a typical typeof arg === 'function' check. It produces the following error:

This expression is not callable. Not all constituents of type '(() => T) | (T & Function)' are callable. Type 'T & Function' has no call signatures.

Is there a way to make it work without using type guard function?

type Initializer<T> = T | (() => T)

function correct(arg: Initializer<string>) {
    return typeof arg === 'function' ? arg() : arg
}

function wrong<T>(arg: Initializer<T>) {
    return typeof arg === 'function' ? arg() : arg // error here
}

const isFunction = (arg: any): arg is Function => typeof arg === 'function'

function correct_2<T>(arg: Initializer<T>) {
    return isFunction(arg) ? arg() : arg
}
3 Answers

You can write:

type Initializer<T> = T extends any ? (T | (() => T)) : never

function correct<T>(arg: Initializer<T>): T {
    return typeof arg === 'function' ? arg() : arg // works
    // arg is Initializer<T> & Function in the true branch
}

const r1 = correct(2) // const r1: 2
const r2 = correct(() => 2) // const r2: number

In the original version, arg is resolved to (() => T) | (T & Function) in the true branch. TS apparently can't recognize for this union function type, that both constituents are callable. At least in above version, it is clear for the compiler that you can invoke arg after a function check.

Might also be worth to create a github issue for this case in the TypeScript repository - in my opinion T & Function should represent some (wide) type of function.

Using instanceof Function to check if arg is callable works nicely:

type Initializer<T> = T | (() => T)

function fixed<T>(arg: Initializer<T>) {
  // Instead of using `typeof`:
  // return typeof arg === 'function' ? arg() : arg // error here

  // use `instanceof`
  return arg instanceof Function ? arg() : arg
}

This was originally described by kentcdodds on a GitHub issue related to this.

I tried a different approach than that in accepted answer, by forbidding T (expected resolved value) to be a function. It seems to work in most use cases, unless you are trying to produce functions from the initializer.

type Initializer<T> = T extends Function ? never : T | (() => T);

function foo<T>(r: Initializer<T>): T {
  return typeof r === 'function' ? r() : r;
}


const valOK = foo('2');
const funOK = foo(() => 4);

const funError = foo((a: number, b: number) => a + b);  // Expected error

Playground link

Related