Reputation: 9295
Let's say I have a number of APIs which are all dictionaries of async methods, e.g.:
interface MyApi {
foo(a: string): Promise<number>;
bar(a: number, b: boolean): Promise<string>;
baz(): Promise<void>;
}
interface MyAp2 {
qux(a: string): Promise<void>;
}
And I want to define a type which any such interface (=a dictionary where each property is an async function) implements, but in a way so that interfaces containing properties that are NOT async functions (e.g. {foo: number;}
or {foo(): number}
) would not match.
What could such a type look like?
I tried this:
type Api = {
[name: string]: (...args: any[]) => Promise<any>;
};
But I cannot do
class Something<T extends Api> {
}
new Something<MyApi>();
Due to
TS2344: Type 'MyApi' does not satisfy the constraint 'Api'.
Index signature for type 'string' is missing in type 'MyApi'.
So the problem seems to be that a concrete set of functions does not have a general string index signature.
I managed to get something working like this, but it feels bulky:
type Api<T> = {
[P in keyof T]: (...args: any[]) => Promise<any>;
};
class Something<T extends Api<T>> {
}
Now, new Something<MyApi>()
works, but trying to do new Something<{foo: number;}>()
fails, as intended: 🥳
TS2344: Type '{ foo: number; }' does not satisfy the constraint 'Api<"foo">'.
Types of property 'foo' are incompatible.
Type 'number' is not assignable to type '(...args: any[]) => Promise<any>'.
Is there a cleaner way of defining a type describing a set of async functions that doesn't have to use the "recursive" syntax T extends Api<T>
, i.e., a simple non-generic type which any interface consisting only of async functions will fulfill?
Upvotes: 1
Views: 42
Reputation: 33061
You need to use type
instead of interface
for MyApi
. Please see related answer and official explanation
Just to fill people in, this behavior is currently by design. Because interfaces can be augmented by additional declarations but type aliases can't, it's "safer" (heavy quotes on that one) to infer an implicit index signature for type aliases than for interfaces. But we'll consider doing it for interfaces as well if that seems to make sense
type MyApi = { // CHANGE IS HERE
foo(a: string): Promise<number>;
bar(a: number, b: boolean): Promise<string>;
baz(): Promise<void>;
}
interface MyAp2 {
qux(a: string): Promise<void>;
}
type Api = {
[name: string]: (...args: any[]) => Promise<any>;
};
class Something<T extends Api> {
}
new Something<MyApi>(); // ok
In other words: interfaces
are not indexed by the default whereas type
are
You can also check my article
Please consider using interfaces
over types
. interfaces
are safer. If you declare two interfaces with same name in different files but these files contain import
/export
there will be no declaration merging because it would be two different modules
Upvotes: 2