如何定义仅包含异步方法的字典的TypeScript超类型?
需求背景
我们有多个仅包含异步方法的API接口,示例如下:
interface MyApi { foo(a: string): Promise<number>; bar(a: number, b: boolean): Promise<string>; baz(): Promise<void>; } interface MyApi2 { qux(a: string): Promise<void>; }
需要定义一个超类型约束,实现:
- 所有仅包含异步方法的接口都能通过约束
- 包含非异步成员(如
{foo: number;}或{foo(): number})的接口会被拦截报错
现有方案的问题
带索引签名的类型(无法使用)
最初尝试定义带字符串索引签名的类型:
type Api = { [name: string]: (...args: any[]) => Promise<any>; };
但用于泛型约束时会触发错误:
class Something<T extends Api> { } new Something<MyApi>(); // TS2344 错误
错误信息:
TS2344: Type 'MyApi' does not satisfy the constraint 'Api'.
Index signature for type 'string' is missing in type 'MyApi'.
原因是具体接口没有声明通用字符串索引签名,无法匹配带索引的Api类型。
递归泛型约束(可行但繁琐)
后来实现了递归泛型约束的方案,虽然能满足需求,但写法不够简洁:
type Api<T> = { [P in keyof T]: (...args: any[]) => Promise<any>; }; class Something<T extends Api<T>> { }
该方案可以正常通过new Something<MyApi>(),也会对非异步接口(如{foo: number;})报错,但T extends Api<T>的递归写法不够直观。
更简洁的非递归解决方案
可以直接在泛型约束中使用映射类型,无需额外定义泛型Api<T>,写法更简洁直观:
class Something<T extends { [K in keyof T]: (...args: any[]) => Promise<any> }> { }
效果验证
- 合法异步接口:
new Something<MyApi>()正常通过类型检查 - 非法非异步接口:
new Something<{foo: number;}>()触发预期错误:TS2344: Type '{ foo: number; }' does not satisfy the constraint '{ [K in keyof { foo: number; }]: (...args: any[]) => Promise
; }'.
Types of property 'foo' are incompatible.
Type 'number' is not assignable to type '(...args: any[]) => Promise'.
可选优化:保留原方法类型(更严谨)
如果需要保留原方法的参数和返回值类型,而非用any模糊处理,可以调整约束为:
class Something<T extends { [K in keyof T]: (...args: Parameters<T[K]>) => Promise<ReturnType<T[K]> extends Promise<infer R> ? R : never> }> { }
这个版本会严格验证每个方法确实是返回Promise的异步函数,同时保留原方法的类型信息。
内容的提问来源于stack exchange,提问作者JHH

