如何区分值和回调参数类型

How to differentiate between value and callback argument type

这个片段

function problem<T>(callback: T | (() => T)) : T {
    return typeof callback === 'function' ? callback() : callback;
}

产生错误

This expression is not callable.
  Not all constituents of type '(() => T) | (T & Function)' are callable.
    Type 'T & Function' has no call signatures. ts(2349)

我花了一段时间才明白 Function 本身确实没有多大意义,因为我需要的是一个 无参数 函数。

在像 T = number 这样的特殊情况下,我可以简单地切换到测试 typeof callback === 'number' 并完成。但是,我需要一个通用的解决方案。

我愿意

我也愿意接受其他选择(我的主要目的是了解细节)。

有哪些可能性?

问题解释

function myfun(s: string) {return s;}和调用

problem(myfun);

类型 T = (s: string) => string 得到正确推断。不应该有像myfun()这样的回调调用;相反,myfun 应该 returned。但是,它出错了,因为 typeof callback === 'function' 成立。

更新

我错误地假设 检查 callback instanceof Function 是 AFAIK 完全相同。

这是不同的,如 playground from the answer 所示。但是,添加

const myfun = (s: string) => s;
console.log(problem(myfun));

说出来

Argument of type '(s: string) => string' is not assignable to parameter of type 'string | (() => string)'.
  Type '(s: string) => string' is not assignable to type '() => string'.ts(2345)
doAfter.ts(57, 21): Did you mean to call this expression?

这听起来像这行的错误

console.log(problem<(s: string) => string>(myfun));

编译并且没有类型歧义。但是,它不起作用,undefined 得到 returned 而不是 myfun 本身。

可能重复

链接问题的答案也没有解决我的问题:

我的第二个问题“(如何)对无参数函数进行运行时测试”也没有出现在潜在的重复问题中。

通过 instanceof 检查应该就足够了:

function problem<T>(callback: T | ((...args: any[]) => T)): T {
  return callback instanceof Function ? callback() : callback;
}

const functionReturnsFromParameter = (s: string) => s;
const callbackReturnsString = () => 'Hello, World from function!';
const justAString = 'Hello, World from constant!'

console.log(problem(functionReturnsFromParameter))
console.log(problem(callbackReturnsString));
console.log(problem(justAString));

Playground

我明白你现在想做什么了。

目标:如果给定的回调不是无参数的,那么它应该被推断为T类型并直接返回。如果它是无参数的,它应该被推断为 () => T 并被调用。为此,我建议使用 overload signature 像这样:

function problem<T extends () => any>(callback: T): ReturnType<T>;
function problem<T>(callback: T): T;
function problem(callback: unknown): unknown {
    /* ... */
}

然后,在使用它时会推断出以下类型:

const test0 = problem("") // literal type: ""
const test1 = problem(() => "") // string
const test2 = problem((s: string) => s) // (s: string) => string

最后,您需要更改 problem 实现中的检查,以便它可以确定给定的回调是否为函数以及它是否接受参数。值得庆幸的是,在 javascript 中,函数有一个 .length 属性,指示所需参数的数量(对可变函数和默认参数有警告)。所以:

return callback instanceof Function && !callback.length ? callback() : callback;

应该可以。


Playground Link.