Home > Net >  Is it possible to wrap a function so that the wrapper has the same arguments plus another argument w
Is it possible to wrap a function so that the wrapper has the same arguments plus another argument w

Time:12-31

My goal here is to wrap an API function so that the wrapper has the same arguments as the API function and then also has one additional final parameter. The API function is very generic so the wrapper needs to take the types and parameters from this inside function.

My reasoning is that I need to enhance the API function with additional optional arguments. For another developer using this wrapper function, it would be an awful experience to have this optional argument as the first argument.

My current attempt is as follows:

const insideFunc = (a: string): string => {
  return a
}

const wrapperFunc = <F extends (...args: any[]) => any>(
  fn: F
): ((b?: string, ...args: Parameters<F>) => [ReturnType<F>, string]) => {
  return (b?: string, ...args: Parameters<F>):[ReturnType<F>, string] => {
    return [fn(...args), b]
  }
}

This is almost what I need however the issue is that the parameter b has to be before the arguments of the inside function.

In a parallel universe, the solution would have the rest arguments before the new parameter as follows:

const insideFunc = (a: string): string => {
  return a
}

const wrapperFunc = <F extends (...args: any[]) => any>(
  fn: F
): ((...args: Parameters<F>, b?: string) => [ReturnType<F>, string]) => {
  return (...args: Parameters<F>, b?: string):[ReturnType<F>, string] => { //Observe the difference in argument order.
    return [fn(...args), b]
  }
}

However, this errors due to the rest arguments having to be the final argument.

Is there another way of solving this so that the inside function arguments can be first in the list?

CodePudding user response:

In argument lists of functions the spread must come after other arguments. However, the same is not true for tuple types.

That means you could declare args like:

(...args: [...args: Parameters<F>, b: string])

Note that each member of this tuple is named, which helps preserve intellisense hints about the names of the original arguments.

It does mean you have to parse the args yourself though, but that's not hard:

const originalArgs = args.slice(0, -1)
const b = args[args.length - 1]
return [fn(...originalArgs), b]

Which seems to work when used like:

const insideFunc = (name: string, age: number, likes: string[]): string => {
  return `${name} is ${age} and likes ${likes.join(', ')}`
}

const fn = wrapperFunc(insideFunc)

console.log(fn(
    'Alex',
    39,
    ['cool stuff', 'awesome things'],
    'and also snowboarding'
))
//-> ["Alex is 39 and likes cool stuff, awesome things", "and also snowboarding"] 

And when you hover over the fn here you can see the argument names are preserved in the reported type:

const fn: (
  name: string,
  age: number,
  likes: string[],
  b: string
) => [string, string]

Working playground example

  • Related