Reputation: 11
I currently rewrite a lib from JavaScript to TypeScript and needs to define proxy functions. They should accept all types and amount of params give them to the origin function like this:
async function any(table: string, columns?: string[], conditions?: object): Promise<object[]>
async function any(table: string, conditions?: object): Promise<object[]>
async function any(queryFunction: object): Promise<object[]>
async function any(...params: any[]): Promise<object[]> {
return Promise.resolve([])
}
async function one(table: string, columns?: string[], conditions?: object): Promise<object>
async function one(table: string, conditions?: object): Promise<object>
async function one(queryFunction: object): Promise<object>
async function one(...params: any[]): Promise<object> {
return (await any(...params))[0]
}
But tsc
give me the error A spread argument must either have a tuple type or be passed to a rest parameter.
.
The solutions for the issue I found doesn't really fit in my use case. I don't want to handle all possible param variations in the body of the proxy function, because it would be a big overhead. Also I don't want to have a ...param: any[]
overloading definition in the first function, because the param variations should be strict.
So someone have an idea how to solve that kind of issue in a good way?
Thanks and Greetings.
Upvotes: 1
Views: 87
Reputation: 328362
Overloads in TypeScript are fairly limited in usefulness:
You can call an overloaded function with a single statically-known call signature and that's it. In your code example, you could call any("abc", ["def"])
or any("abc", {c: 0})
or any({q: 1})
, but you can't call it with anything like a union of its possible parameters. This is suggested in microsoft/TypeScript#14107, but it has not been implemented.
When you implement an overloaded function you generally widen the function parameters to make it easy on yourself, but then the compiler no longer keeps track of the particular ways the function could actually be called. So inside of your implementation of one()
, you've got params
as type any[]
. The compiler doesn't know that it will actually be either [string, string[]?, object?]
or [string, object?] or
[object]. It's just
any[]. So you can't call
any(...params), because
any()doesn't accept an argument list of type
any[]`. There's a suggestion to support keeping track of the call signatures inside the implementation at microsoft/TypeScript#22609, but again, this is not implemented.
These two issues together make it tough to approach things the way you're doing. If you really want to use overloads, you should probably just use a type assertion to suppress the error and move on, noting that this is not making your compilation quieter, not safer:
async function one(...params: any[]): Promise<object> {
return (await any(...params as [string]))[0]; // no error now
}
A common use for overloads is that different call signatures can have different return types. But in your case they all return the same return type for a given function, and that suggests a different approach: write a single call signature that takes a rest parameter whose type is a union of tuples. It could look like this:
type MyParams =
[table: string, columns?: string[], conditions?: object] |
[table: string, conditions?: object] |
[queryFunction: object]
async function any(...params: MyParams): Promise<object[]> {
return Promise.resolve([])
}
async function one(...params: MyParams): Promise<object> {
return (await any(...params))[0]
}
Here the MyParams
type represents the union of possible ways of calling any()
and one()
. Inside the implementation of one()
, the compiler knows that params
is the same exact type as the type passed to any()
, so it lets you call it with no error. And you can verify that calling any()
and one()
behaves very similarly to the overloaded version:
// 1/3 any(table: string, columns?: string[] | undefined,
// conditions?: object | undefined): Promise<object[]>
// 2/3 any(table: string, conditions?: object | undefined): Promise<object[]>
// 3/3 any(queryFunction: object): Promise<object[]>
any("abc", ["def"]); // okay
any("abc", { c: 0 }); // okay
any({ q: 1 }); // okay
// 1/3 any(table: string, columns?: string[] | undefined,
// conditions?: object | undefined): Promise<object>
// 2/3 any(table: string, conditions?: object | undefined): Promise<object>
// 3/3 any(queryFunction: object): Promise<object>
one("abc", ["def"]); // okay
one("abc", { c: 0 }); // okay
one({ q: 1 }); // okay
Indeed, when you look at the IntelliSense hints when you call one()
or any()
, you are actually shown multiple call signatures as if they were "genuine" overloads.
Upvotes: 2