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

时间:2017-09-09 21:14:21

标签: 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: 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>;

因为它期望一个带有3个参数的函数(最后一个是回调),我猜它会匹配前3个定义中的一个。但是,当我尝试使用cbCall时这样:

CL.cbCall(
    fs.readFile,
    fs,
    'filename',
    { encoding: 'utf-8' }
);

我收到错误:

Argument of type '{ encoding: string; }' is not assignable to parameter of type '(err: ErrnoException, data: Buffer) => void'.

所以它以某种方式预期arg2应该是cb应该是什么。

如果我删除Arg2模板参数并将其替换为object,则可以使用,但这对我的用例来说不够通用。

请解释为什么会这样,以及我是否能达到我想要实现的目标。

编辑:使用cbCall<string, string, object>(...)也可以,但是再次失败了。

编辑:我将示例简化为:

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'); //

更改前两行的顺序也可以修复它。 但是不应该使用第一个匹配声明而不是最后一个吗?

1 个答案:

答案 0 :(得分:3)

类型参数分配在重载解决之前进行,并且不会在故障时回溯,因此有时您会遇到可以通过选择不同的重载来避免的错误。我还没有找到类型参数匹配算法的一个很好的解释,但这个issue解释了一下。

由于您的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;
}
但是,它仍然不是很好。在崩溃类型检查器(issue)之后,我自己对泛型变得更加保守了。在一个版本中工作的复杂解决方案通常会在下一个版本中解决,并且有很多未解决的问题没有取得多大进展。希望将来会更稳定。

编辑:想到另一种选择。如果传递给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
}

并在fun: CallbackFun<Arg1, Arg2, Ret>的定义中使用cbCall