使用通用可选参数的 Typescript 函数重载

2023-11-22

我正在尝试编写一个高阶函数来包装输入函数并缓存最近调用的结果作为副作用。基本功能(withCache)看起来像这样:

function cache(key: string, value: any) {
    //Some caching logic goes here
}

function withCache<R>(key: string, fn: (...args: any[]) => R): (...args: any[]) => R {
    return (...args) => {
        const res = fn(...args);
        cache(key, res);
        return res;
    }
}

const foo = (x: number, y: number) => x + y;
const fooWithCache = withCache("foo", foo);
let fooResult1 = fooWithCache(1, 2); // allowed :)
let fooResult2 = fooWithCache(1, 2, 3, 4, 5, 6) // also allowed :(

现在我知道我可以使用函数重载使这种类型安全 - 在一定程度上 - 如下所示:

function withCache<R>(key: string, fn: () => R): () => R
function withCache<R, T1>(key: string, fn: (a: T1) => R): (a: T1) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b: T2) => R): (a: T1, b: T2) => R
function withCache<R>(key: string, fn: (...args: any[]) => R): (...args: any[]) => R {
    // implementation ...
}

const foo = (x: number, y: number) => x + y;
const fooWithCache = withCache("foo", foo);
let fooResult1 = fooWithCache(1, 2); // allowed :)
let fooResult2 = fooWithCache(1, 2, 3, 4, 5, 6) // not allowed :)

当我尝试允许带有可选参数的函数时,麻烦就来了(最后一个重载是新的):

function withCache<R>(key: string, fn: () => R): () => R
function withCache<R, T1>(key: string, fn: (a: T1) => R): (a: T1) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b: T2) => R): (a: T1, b: T2) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b?: T2) => R): (a: T1, b?: T2) => R
function withCache<R>(key: string, fn: (...args: any[]) => R): (...args: any[]) => R {
    // implementation ...
}

const foo = (x: number, y?: number) => x + (y || 0);
const fooWithCache = withCache("foo", foo);
let fooResult1 = fooWithCache(1); // allowed :)
let fooResult2 = fooWithCache(1, 2) // not allowed, but should be :(

问题似乎是 Typescript 选择了错误的重载withCache,结果是签名fooWithCache is (a: number) => number。我希望fooWithCache的签名为(a: number, b?: number) => number, 就像foo。 有没有什么办法解决这一问题?

(As a side note, is there any way to declare the overloads so I don't have to repeat each overload's function type (...) => R?)

Edit:

解决了我关于不重复函数类型的第二个问题:只需定义它!

type Function1<T1, R> = (a: T1) => R;
// ...
function withCache<T1, R>(fn: Function1<T1, R>): Function1<T1, R>;

Edit:

对于异步函数来说,这将如何工作(假设您想缓存结果而不是 Promise 本身)?你当然可以这样做:

function withCache<F extends Function>(fn: F) {
  return (key: string) =>
      ((...args) => 
        //Wrap in a Promise so we can handle sync or async
        Promise.resolve(fn(...args)).then(res => { cache(key, res); return res; })
    ) as any as F; //Really want F or (...args) => Promise<returntypeof F>
}

但这样使用起来就不安全了同步功能:

//Async function
const bar = (x: number) => Promise.resolve({ x });
let barRes = withCache(bar)("bar")(1).x; //Not allowed :)

//Sync function
const foo = (x: number) => ({ x });
let fooRes = withCache(foo)("bar")(1).x; //Allowed, because TS thinks fooRes is an object :(

有办法防范这种情况吗?或者编写一个对两者都安全工作的函数?

摘要:@jcalz 的答案是正确的。在可以假定同步函数的情况下,或者可以直接使用 Promise 而不是它们解析的值的情况下,断言函数类型可能是安全的。然而,如果没有一些现有技术,上述同步或异步场景是不可能实现的。未实现的 language 改进.


通过向下浏览列表并选择第一个匹配的重载来选择重载。

检查以下代码,该代码已成功编译:

declare let f: (a: any, b?: any) => void;
declare let g: (a: any) => void;
g = f; // okay

功能f是一个接受一个或两个参数的函数,而g被声明为一个接受 1 的函数。您可以分配该值f到变量g,因为您可以在任何可以调用一个参数的函数的地方调用任何一个或两个参数的函数。事实上,第二个参数是optional这使得这项任务有效。

您还可以做其他作业:

f = g; //okay

因为您可以在任何可以调用一两个参数的函数的地方调用一个参数的任何函数。这意味着这两种类型的函数是可以相互赋值的(尽管它们并不等价,这有点不合理)。


如果我们只看这两个重载:

function withCache<R, T1>(key: string, fn: (a: T1) => R): (a: T1) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b?: T2) => R): (a: T1, b?: T2) => R

上述讨论f and g意味着任何与这些重载之一匹配的内容都将与另一个重载匹配。因此,无论您先列出哪个,都会被选择。抱歉,您实际上无法同时使用它们。

此时,我可以建议您开始提出一组折衷的重载,以提供合理的行为,但让我们备份一下:


你不只是想要一个类型安全的版本吗withCache()?这个怎么样:

function withCache<F extends Function>(key: string, fn: F): F {     
    // implementation ...
}

没有重载,并且返回值始终与fn范围:

const foo = (x: number, y?: number) => x;
const fooWithCache = withCache("foo", foo); // (x: number, y?: number) => number
let fooResult1 = fooWithCache(1); // allowed :)
let fooResult2 = fooWithCache(1, 2) // allowed :)

那对你有用吗?祝你好运!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用通用可选参数的 Typescript 函数重载 的相关文章

随机推荐

  • 在活动布局中使用 Admob 时的白色状态栏

    我正在使用 CoordinatorLayout 和relativelayout 在底部显示广告 但我得到的是白色状态栏 当我删除相对布局和 adview 时 一切正常 当我将 CoordinatorLayout 包裹在relativelay
  • 在 JavaScript 函数中使用三元运算符

    我是 Javascript 新手 正在努力解决这些三元运算符的问题 我有这个小代码段 const x MSys inShip ship launch if x send command x 虽然这工作效率足够高 但我很好奇是否可以在函数调用
  • 画布扭曲绘图。如何获得设置尺寸和样式尺寸之间的比例因子?

    我有这个画布
  • 在Python中转义html?

    我有一个 img src string but string可能包含 我该怎么做才能逃脱它 Example string test jpg img src test 不起作用 在Python 3 2中一个新的html引入了模块 该模块用于从
  • n 位整数的平方与两个 n 位整数的乘法

    免责声明 家庭作业问题 我正在寻找提示 F Lake 教授告诉他的班级 对 n 位整数进行平方比对两个 n 位整数进行乘法渐近更快 他们应该相信他吗 我相信通过移位 加法将两个 n 位整数相乘是一个 O n 操作 但我不明白为什么对一个 n
  • 使用 CSS 和 IE 旋转文本

    我需要用 CSS 旋转文本 我有以下样式规则 但它们似乎不适用于 Internet Explorer footer descr span moz transform rotate 20deg Firefox o transform rota
  • matplotlib:以 None 作为值绘制 numpy 数组

    我有一个看起来像这样的数组 k numpy array 1 0 001 1 1 0 002 None None 1 2 0 003 0 99 0 004 我想绘制不是的值 None None 并保留数组值的索引 也就是说 只要有间隙 我就想
  • Go中如何实现策略模式?

    这是我试图解决的一般问题 一组包从一个源收集数据并将其发送到多个通道 第二组包从多个通道收集数据并将其写入一个源 这组包需要转换多种格式的数据 这似乎是策略模式的完美案例 但我不确定如何最好地使其在 Go 中发挥作用 一般来说 不要迷失在构
  • 如何在 Jenkinsfile 中获取 shell 脚本的输出?

    在 Jenkinsfile Groovy 脚本阶段 假设我想发出一个 linux 命令来输出字符串的行和列 并且想要获取某一行的输出中的第 n 列 此类命令的一个示例是 ls al 那我这样做对吗 stage Get dir size sh
  • 如何使用 CSS 将图像大小调整为其自身的百分比?

    我正在尝试使用图像本身的百分比来调整图像的大小 例如 我只想将图像大小调整为 50 将图像缩小一半 但申请width 50 会将图像大小调整为容器元素 可能是父元素 的 50 例如 问题是 我可以在不使用 JavaScript 或服务器端的
  • 如何使用 micrometer-brave 在 spring-cloud-gateway 中获取trace-id

    我想在 Spring Cloud Gateway 中的每个请求的日志中显示traceId 然而 traceId和spanId只是空的 日志配置如下 logging pattern level 5p TRACE ID X traceId SP
  • ServiceStack.Redis:无法连接:sPort:0

    有时 并非总是 我得到 无法连接 sPort 0 使用 ServiceStack Redis 时尝试从 Redis 获取值时出错 有谁知道这可能意味着什么 我正在使用 PooledRedisClientManager 来获取客户端 我相信你
  • Firebase 3.0 令牌:[错误:Firebase Auth ID 令牌没有“kid”声明]

    我目前正在使用 firebase 3 0 开发一个 node js 服务 该服务由使用 firebase 2 4 的 Web 应用程序调用 我正在发送当前用户Firebase ID 令牌 Auth getAuth token 在我的标头调用
  • ng14 中具有强类型表单的 FormBuilder

    我有以下表格 const enum Fields FirstName firstName LastName lastName interface FormType Fields FirstName FormControl
  • 检索安全描述符并获取 FileSystemRights 编号

    Using Get Acl我正在尝试获取文件夹的访问权限 问题是 对于某些组 我得到的是号码而不是访问类型 下面的例子 get acl C TestFolder access FileSystemRights 536805376 Acces
  • Laravel 5.6 getRouteKeyName() 不起作用

    这是我到目前为止的代码 Web php Route get uri PageController show gt name page show 页面控制器 Show the requested page public function sh
  • 将 .jar 转换为 OSX 可执行文件?

    我制作了一个 Java 应用程序 我想在 Windows OSX 和 Linux 上分发它 而不分发 jar 文件 我使用了很棒的 Windows exe 包装器http launch4j sourceforge net 创建一个包含我的图
  • Android ListView 具有多种布局

    我必须显示具有不同类型视图的列表 所以我必须定义一个带有适配器的 ListView 我必须在其中扩展多个视图 我已经经历过example给定 但问题是我的列表不对称 就像示例中每次在 4 个项目后重复标题一样 所以我面临着重复使用物品的问题
  • 如何在跨文件预处理时获取唯一值

    PROBLEM 我需要一种使用预处理器指令生成唯一值的方法 目的是每次调用宏时 它都会有一个唯一的integral标识符 但它应该在文件中保留其价值 有点像预处理器计数器 用于记录调用函数的次数 更多信息 我使用的宏是 define LOG
  • 使用通用可选参数的 Typescript 函数重载

    我正在尝试编写一个高阶函数来包装输入函数并缓存最近调用的结果作为副作用 基本功能 withCache 看起来像这样 function cache key string value any Some caching logic goes he