接受带有回调最后一个参数的函数的TypeScript泛型

接受带有回调最后一个参数的函数的TypeScript泛型,typescript,generics,Typescript,Generics,我在将泛型应用于TypeScript定义时遇到问题 定义如下: export function readFile(path: PathLike | number, options: { encoding?: null; flag?: string; } | undefined | null, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; export function readFile(path:

我在将泛型应用于TypeScript定义时遇到问题

定义如下:

export function readFile(path: PathLike | number, options: { encoding?: null; flag?: string; } | undefined | null, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void;
export function readFile(path: PathLike | number, options: { encoding: string; flag?: string; } | string, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
export function readFile(path: PathLike | number, options: { encoding?: string | null; flag?: string; } | string | undefined | null, callback: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void;

export function readFile(path: PathLike | number, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void;
如您所见,它们接受2到3个参数,最后一个参数始终是回调

现在我要说的是:

function cbCall<Ret, Arg1, Arg2>(
    fun: (arg1: Arg1, arg2: Arg2, cb: (error: any, result: Ret) => any) => any,
    obj: any,
    arg1: Arg1,
    arg2: Arg2
): Context<Ret>;
我得到一个错误:

Argument of type '{ encoding: string; }' is not assignable to parameter of type '(err: ErrnoException, data: Buffer) => void'.
因此,它希望
arg2
成为
cb
应该是的样子

如果我删除
Arg2
模板参数并将其替换为
object
,它会工作,但这对于我的用例来说不够通用

请解释为什么会发生这种情况,以及我是否能够实现我正在努力实现的目标

编辑:使用
cbCall(…)
也很有效,但同样也达不到目的


编辑:我将示例简化为:

function fun(arg: string, cb: (err: string, res: string) => void);
function fun(cb: (err: string, res: string) => void): void;
function fun(arg: any, cb?: any): void {
    // noop
}

function call<Ret, Arg>(fun: (arg: Arg, cb: (err: string, res: Ret) => void) => any, arg: Arg) {
    // noop
}

// this works
call<string, string>(fun,'test');

// Argument of type '"test"' is not assignable to parameter of type '(err: string, res: string) => void'.
call(fun,'test'); //
函数乐趣(arg:string,cb:(err:string,res:string)=>void);
函数fun(cb:(err:string,res:string)=>void:void;
函数乐趣(arg:any,cb?:any):无效{
//努普
}
函数调用(乐趣:(arg:arg,cb:(err:string,res:Ret)=>void)=>any,arg:arg){
//努普
}
//这很有效
呼叫(乐趣,‘测试’);
//类型为“test”的参数不能分配给类型为“(err:string,res:string)=>void”的参数。
呼叫(乐趣,‘测试’)//
更改前两行的顺序也可以修复此问题。
但是不应该使用第一个匹配声明而不是最后一个吗?

类型参数赋值发生在重载解析之前,并且在失败时不会回溯,因此有时您会遇到错误,可以通过选择不同的重载来避免。我还没有找到一个关于类型参数匹配算法的好解释,但这解释了一点

由于您的
fun
有一个重载,将选择
readFile
的最后一个重载,因为它被认为是最不具体的重载。作为类型错误的解决方法,您可以在调用
cbCall
的模块中自己提供所需的重载:

declare module 'fs' {
  function readFile(path: fs.PathLike | number, options: { encoding?: string | null; flag?: string; } | string | undefined | null, callback: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void;
}
不过还是不太好。在破坏了类型检查器()之后,我自己对泛型的使用变得更加保守了一些。在一个版本中工作的复杂解决方案经常在下一个版本中中断,并且有相当多的开放问题没有看到太多的进展。希望它在未来会变得更加稳定

编辑:只是想到了另一个选择。如果传递给
cbCall
的回调函数在没有
options
参数的情况下共享最后一个重载,则还可以将该重载添加到
fun
类型中:

interface CallbackFun<Arg1, Arg2, Ret> {
  (arg1 : Arg1, arg2 : Arg2, cb : (error : any, result : Ret) => any) : any
  (arg1 : Arg1, cb : (error : any, result : Ret) => any) : any
}
interface CallbackFun{
(arg1:arg1,arg2:arg2,cb:(错误:any,结果:Ret)=>any):any
(arg1:arg1,cb:(错误:any,结果:Ret)=>any):any
}

并在
cbCall
的定义中使用
fun:CallbackFun

在此处删除链接,以防我没有时间完成此回答:谢谢您的精彩回答。“readFile的最后一个重载将被选中,因为它被认为是最不具体的一个”-我没有想到这一点,我想这是有道理的。我决定在这种情况下只显式地传递模板参数,我认为这是最不可能破坏的。
interface CallbackFun<Arg1, Arg2, Ret> {
  (arg1 : Arg1, arg2 : Arg2, cb : (error : any, result : Ret) => any) : any
  (arg1 : Arg1, cb : (error : any, result : Ret) => any) : any
}