我可以在TypeScript中表达此高阶函数签名吗?

时间:2020-06-09 11:24:49

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

我具有以下功能:

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

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

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) => { ... });时,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)));
};

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

1 个答案:

答案 0 :(得分:1)

我认为您至少要努力在打字稿中实现这一点,至少现在很难。我希望能够给出一个正确的答案,但是我找不到一个好的解决方案。

部分问题是尝试定义一个HOF,该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);

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

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

当函数采用相同类型的参数时,我们可以提供可行的类型,因为我们可以将其键入为:[string, ...T[]]。但是,这将是非常有限的。