简体   繁体   中英

Typescript Inferring parameter types from function records

I have a object fn with name:function pairs that can take arbitrary parameters. I want to be able to map these functions in order to wrap them with another default function to handle errors in a single location. I have the following code which doesn't even compile giving A spread argument must either have a tuple type or be passed to a rest parameter. on fn[f](...args)

const fn = {
    ...userActions,
    ...companyActions,
    ...dataActions
};

type A<T extends Array<any>, U> = Record<keyof typeof fn, (...args: T) => Promise<U>>;

function map<T extends Array<any>, U>(f: keyof A<T, U>, ...args: T) {
    fn[f](...args);
}

I can wrap functions using the code below but it requires importing the relevant function into the caller file and passing it into fn parameter

async function run<T extends Array<any>, U>(fn: (...args: T) => Promise<U>, ...temp: Parameters<typeof fn>) {
    await fn(...temp); // works but needs to pass the function itself.
}

Is there a way to get around the issue on first piece of code? Basically I want to infer the parameter type by using just the key from a record.

The problem here is that, in your map function, Typescript does not infer that keyof typeof fn is one particular key of fn , but rather that it is the union of all possible keys of fn. It won't resolve the particular key until you actually pass it a concrete type when you call map. This in turn means that args is being resolve as the intersection of all possible args of all functions in fn --not a spread of one particular args .

This is a typescript limitation as TS won't resolve the generic inside a function until it is actually passed a concrete type.

So, unfortunately, I do think some assertion is necessary inside the map function, but that doesn't prevent you from having good type safety on the inputs and outputs of your map function.

That said, I wasn't 100% clear on how you were hoping to use your map function, but hopefully this helps understand a way to type this kind of problem:

const userActions = {
  func1: (x:string)=>parseInt(x)
}
const companyActions = {
  func2: (x:number)=>x.toString()
}
const fn = {
    ...userActions,
    ...companyActions,
};

 function map<T extends keyof typeof fn>(f: T, ...args: Parameters<typeof fn[T]>):ReturnType<typeof fn[T]> {
    return (fn[f] as any)(args) as ReturnType<typeof fn[T]>;
}

map("func1", "foo")
map("func2", 3)

//these will error
map("func1", 3)
map("func2", "string")

With a playground . This will ensure the inputs and output of map are typed based on fn .

I removed the Promise as I don't think that's fundamental to your question. It's trivial to add that back in. Please post more info if this isn't sufficient.

I think your mistake is, you didn't specify the type of the fn object, so the typescript couldn't understand whatever you want from him.

As a test I have specified the type the following way:

const fn: { [key: string]: (...args: any[]) => any } = {
    ...userActions,
    ...companyActions,
    ...dataActions
};

This way you letting the typescript to know that any of the fn properties can take a certain amount of parameters.

And now it compiles and works when I call the map function!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM