【问题标题】:TypeScript generic accepting a function with callback last argumentTypeScript 泛型接受带有回调最后一个参数的函数
【发布时间】:2017-09-10 09:30:37
【问题描述】:

我在将泛型应用于 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&lt;string, string, object&gt;(...) 也可以,但又一次失败了。


编辑:我将示例简化为:

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

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

【问题讨论】:

标签: typescript generics


【解决方案1】:

类型参数分配发生在重载解决之前,并且不会回溯失败,因此有时您会遇到可以通过选择不同的重载来避免的错误。我还没有找到关于类型参数匹配算法的一个很好的解释,但是这个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
}

并在cbCall的定义中使用fun: CallbackFun&lt;Arg1, Arg2, Ret&gt;

【讨论】:

  • 感谢您的精彩回答。 “ readFile 的最后一个重载将被选中,因为它被认为是最不具体的” - 我没有想到,我想它确实有道理。在这种情况下,我决定只显式传递模板参数,我认为这不太可能破坏。
猜你喜欢
  • 2021-03-31
  • 2022-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多