Reputation: 777
suppose I have an abstract class which takes a generic
export abstract class RegisterableClass<InstanceType>
and an implementer below as such:
class UserService extends RegisterableClass<IUserService> implements IUserService {
someConstant: 42,
async getAllUsers: (): User[] => {...}
}
with an interface like:
interface IUserService {
someConstant: number;
getAllUsers: () => Promise<User[]>;
}
I want to ensure that the passed generic, IUserService
, in this particular case, are all asynchronous.
Can I do this statically with TypeScript somehow?
maybe using Extract<keyof IUserService, Function>
? and then passing that to something in turn?
That is, if you attempt to extend RegisterableClass
and pass it a generic in which not all functions are async, typescript will not compile
Upvotes: 0
Views: 104
Reputation: 329838
If I understand correctly you'd like to constrain the type parameter passed to RegisterableClass
to be something whose methods are all asynchronous (meaning that they return promises).
If so, you can achieve that like this:
type AllMethodsAreAsync<I> = {
[K in keyof I]: I[K] extends (...args: any) => infer R ?
R extends Promise<any> ? I[K] : (...args: any) => Promise<R> :
I[K]
}
export abstract class RegisterableClass<I extends AllMethodsAreAsync<I>> {
// ... something involving I, hopefully
}
The type function AllMethodsAreAsync<I>
will be equivalent to I
if I
is an object type whose function-valued properties return promises. But if I
has any function-valued properties that don't return promises, the corresponding property of AllMethodsAreAsync<I>
will be changed to return a promise instead.
Then, if I extends AllMethodsAreAsync<I>
passes the generic constraint, great. Otherwise, you should get an error telling you exactly what from I
doesn't work. Like this:
// adding this so standalone example works
type User = { u: string };
interface IOkayService {
someConstant: number;
getAllUsers(): Promise<User[]>;
}
type OkayService = RegisterableClass<IOkayService>; // okay
interface IBadService {
someConstant: number;
getAllUsers(): Promise<User[]>;
getSomethingSynchronously(x: string): number;
}
type BadService = RegisterableClass<IBadService>; // error!
// ~~~~~~~~~~~
// Type 'IBadService' does not satisfy the constraint 'AllMethodsAreAsync<IBadService>'.
// Types of property 'getSomethingSynchronously' are incompatible.
// Type '(x: string) => number' is not assignable to type '(...args: any) => Promise<number>'.
// Type 'number' is not assignable to type 'Promise<number>'.
Does that do what you want? Hope that helps; good luck!
Upvotes: 3