简体   繁体   中英

Can I express this higher-order function signature in TypeScript?

I have the following function:

const fn = async (bool, num, str) => {
  // ...
};
  • bool is a boolean,
  • num is a number
  • str is an optional string

I also have the following HOF that allows populating that last optional parameter at call-time if it hasn't been passed through by the caller:

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));
  };
};

This allows writing:

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

How can I fully type hof , knowing that it is not aware of the callback's parameters (ie they can differ from one usage to another)? Is this even possible with TypeScript?

I managed to deal with the returned type via generics:

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

But can those dynamic parameters be typed, so that when using const fn = hof(async (bool, num, str) => {... }); , fn will only accept parameters according to the callback ones? The only difference between the fn signature and the callback one, is that fn allows an undefined str last parameter while the callback has it mandatory.

Not compiling, but something along those lines:

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

Just in case of I'm hitting an XY problem , the real-life usage is MySQL transaction management . This pattern allows me to start a new transaction automatically if no connection is passed through, or use the caller's connection if it started a transaction already. This implementation is being discussed here , but in a nutshell, that my real-life 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)));
};

Shout if you're thinking of a better approach;)

I think that you'll struggle to achieve this in typescript, at least, it'll be difficult as it is now. I'd like to be able to come in with a proper answer but I've not been able to find a good solution.

Part of the problem is trying to define a HOF that takes arguments of any length and type, but always with a string. It's somewhat easier to define your functions as taking objects with given properties, for instance, as:

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

I appreciate that this is quite a different implementation to that which you have in your question, but it's the closest I could get.

The problem with the original solution is that we have a hard time defining a function that takes args which are [string, ...T] , because we can't spread generics , even if we assert that T extends [] . Further, we can't define args as [...T[], string] , because the rest parameter must be the last parameter.

We can provide a workable type when a function takes arguments all of the same type, because we can type it as: [string, ...T[]] . This would be very limiting, though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM