【问题标题】:Can I express this higher-order function signature in TypeScript?我可以在 TypeScript 中表达这个高阶函数签名吗?
【发布时间】:2020-09-28 13:33:06
【问题描述】:

我有以下功能:

const fn = async (bool, num, str) => {
  // ...
};
  • bool 是一个布尔值,
  • num 是一个数字
  • str 是一个可选字符串

我还有以下 HOF 允许在调用时填充最后一个 可选 参数,如果它没有被调用者传递:

const hof = (callback) => {
  return async (...args) => {
    if (args.length) {
      const lastArg = args[args.length - 1];
      if ("string" === typeof lastArg) {
        return callback(...args);
      }
    }
    const s = await findString();
    return callback(...[...args].concat(s));
  };
};

这允许写作:

const fn = hof(async (bool, num, str) => {
  // str will be valued, either by the caller, or by the hof
});

我如何才能完全键入hof,知道它不知道回调的参数(即它们可能因一种用法而异)?使用 TypeScript 是否也能做到这一点?

我设法通过泛型处理返回的类型:

const hof = <R>(
  callback: (...args: any) => Promise<R>
): (...args: any) => Promise<R> => {
  return async (...args) => {
    // ...
  };
};

但是那些动态参数能不能打出来,这样在使用const fn = hof(async (bool, num, str) =&gt; { ... });时,fn就只能接受回调的参数了? fn 签名和回调签名之间的唯一区别是,fn 允许未定义的 str 最后一个参数,而回调是强制的。

不是编译,而是类似的东西:

const hof = <R>(
  callback: (...args: ???, s: string) => Promise<R>
): (...args: ???, s?: string) => Promise<R> => {
  return async (...args) => {
    // ...
  };
};

如果我遇到XY problem,实际用法是MySQL transaction management。这种模式允许我在没有连接通过时自动启动一个新事务,或者如果它已经启动了一个事务,则使用调用者的连接。这个实现正在讨论here,但简而言之,我的现实生活hof

const transactional = (run) => async (...args) => {
  if (args.length) {
    const lastArg = args[args.length - 1];
    if (lastArg && "PoolConnection" === lastArg.constructor.name) {
      return run(...args);
    }
  }
  return _transactional(async (connection) => run(...[...args].concat(connection)));
};

如果您正在考虑更好的方法,请大喊;)

【问题讨论】:

  • 作为旁注"String" === lastArg.constructor.name 很可能是错误的。如果您正在检查字符串原语,则执行typeof lastArg === "string" 就足够了。检查构造函数名称的唯一原因是检查字符串object。但是,为此 1. 您可以使用 lastArg instanceof String 2. 您很可能不应该使用字符串对象。
  • @VLAZ 是的,好点,我的简化示例不是很好。 IRL,我正在检查该参数是否是 PoolConnection 的实例,如上一节所示,但是mysql lib没有公开这种类型,所以我不得不改用构造函数名称,除非你在想有更好的方法吗?
  • 嗯,可能值得为此单独提出一个问题。我肯定会尝试公开它,但是您可以使用instanceof
  • 只是一个快速的问题,当您说 sql 事务时,是否可以进行多次调用,因为不幸的是节点是异步的,事务不会像您期望的那样序列化,尤其是使用相同的连接对象.
  • 这似乎有点像逆向的部分应用;我们可以进一步抽象一下,让 HOF 接受一些函数,并返回一个不接受最后一个参数的函数?

标签: javascript typescript callback signature higher-order-functions


【解决方案1】:

我认为您将很难在 typescript 中实现这一点,至少会像现在这样困难。我希望能够提供正确的答案,但我无法找到好的解决方案。

问题的一部分是试图定义一个 HOF,它接受任何长度和类型的参数,但总是带有一个字符串。将函数定义为具有给定属性的对象会更容易一些,例如:

interface IWithConnection {
    connection?: string;
}

interface IFnArgs {
    bool: boolean;
    num: number;
}

const findString = async () => 'a-connection';

const injectConnection = async <T extends IWithConnection, U>(fn: (x: T) => U) => {
    return async (x: IWithConnection & T) => {
        if (x.connection !== undefined) {
            return fn(x);
        }

        const connection = await findString();

        return fn({ connection, ...x });
    };
};

const fn = ({ bool, num, connection }: IFnArgs & IWithConnection) => `${bool} ${num} ${connection}`;

const fn_ = injectConnection(fn);

我很欣赏这与您在问题中的实现完全不同,但这是我能得到的最接近的实现。

原始解决方案的问题在于,即使我们断言T extends [],我们也很难定义一个接受参数为[string, ...T]because we can't spread generics 的函数。此外,我们不能将args定义为[...T[], string],因为rest参数必须是最后一个参数。

当函数接受所有相同类型的参数时,我们可以提供一个可行的类型,因为我们可以将其键入为:[string, ...T[]]。不过,这将是非常有限的。

【讨论】:

  • 我明白了,谢谢你的解释。您如何看待({ bool, num }, connection),即将connection 移出输入对象?这将允许fn_ 返回(T, connection?) =&gt; R,而回调将使用(T, connection) =&gt; R 进行签名,从而更明确地表明connection 将始终在回调中进行赋值?
  • @sp00m 我认为这很好用 - 困难来自这样一个事实,即在 typescript 中表达多种类型的数组非常困难,当我们想要描述 @987654333 时,这会导致问题@ - 我们可以说 [string, ...T[]],但是所有 args 1-n 都需要是相同的类型,这是我们不想要的。 ({ bool, num }, connection) 表示签名可以是(T, string),这更容易输入
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-16
  • 2012-10-25
  • 2021-02-10
  • 2022-07-01
  • 2020-05-17
相关资源
最近更新 更多