paroxyzm
paroxyzm

Reputation: 1499

Partially specifying the type of a function type in Typescript?

I have a use-case where I have a bunch of functions which have to meet certain criteria, i.e:

  1. each function has to take exactly 2 parameters, which would be a string and an Array<K>
  2. have to return a Promise of unspecified type, which has to be inferred by TS machinery, the type cannot be unknown, any, etc. because it will override the type inference

In essence, I'm looking for some magic type like:

type MagicType<K> = (str: string, keys: Array<K>) => Promise<infer>;

so that these functions would type-check: and return inferred type:

const f1: MagicType<number> = async (str, keys) => {
    return '42'
}
// expecting typeof f1 === (string, Array<number>) => Promise<string> 

const f2: MagicType<number> = async (str, keys) => {
    return keys.length;
}
// expecting typeof f2 === (string, Array<number>) => Promise<number> 

// note lack of `async` keyword
const f_error2: MagicType<number> = (str, keys) => {
    return keys.length;
}
// expecting to be a compilation error, since number is not Promise<unknown>

My trials and (only) failures can be found here: TS-playground

Any help appreciated. I know this is most likely a corner-case, but I'd like to know if it's possible. Many thanks!

Upvotes: 1

Views: 221

Answers (1)

I see two options og handling this case:

  1. you are providing explicit return type:

const f1: MagicType<number, string> = async (str, keys) => '42'
  1. providing extra function in order to infer return type:

type MagicType<K> = (str: string, keys: Array<K>) => Promise<any>;

const infer = <Cb extends MagicType<number>>(cb: Cb) => cb


const infered = infer(async (str, keys) => '42')

const result = infered('hello', [1, 2, 3]) // Promise<string>

Playground

Explanation:

infer - expects one argument cb. I have created extra Cb generic parameter to infer cb argument as much as possible. Once you create a generic for function argument, TS will try to infer it. If you are interested in type inference on function arguments please check my article

Please keep in mind that once you have provided explicit type MagicType<number> for function in :

const f1: MagicType<number> = async (str, keys) => {
    return '42'
}

TypeScript don't do any inference work it just uses MagicType<number>.

Consider this example:

const arr: ReadonlyArray<string> = ['a', 'b'] as const;

You may think that TS is able to figure out that apart from being ReadonlyArray<string> arr is also ['a','b'], but it is not true. Array arr is just a ReadonlyArray<string> it is no more possible to infer literal type of array element.

UPDATE

You can also use :


type MagicType<K> = (str: string, keys: Array<K>) => Promise<unknown>;

const infer = <Type, Cb = MagicType<Type>>(cb: Cb) => cb

const infered = infer<string>(async (str, keys) => '42')

Explanation: infer - function expects two generic parameters:

  • first one Type, represents type of element in the array keys. It refers to K in your example.
  • second one Cb has default parameter, it means that you don't need to provide second parameter to your infer fucntion. Second parameter Cb is just a MagicType<Type>. Hence, just like in your example, you need to provide type of keys element.

We still don't have any geeric for return type of Cb. We don't need, since TS is able to infer it for us.

You can also replace Promise<any> with Promise<unknown>

P.S. I'm not a big fan of using explicit generics.


This case is not strictly related to our problem, but it might be helpful for you:

Consider this very simple example:

const returnType = <Return,>(cb: () => Return) => cb()

const result = returnType(() => 42) // number

TS is able to infer Return generic

Upvotes: 2

Related Questions