是否可以包装一个函数,使包装器具有相同的参数以及位于这些参数之后的另一个参数?

Is it possible to wrap a function so that the wrapper has the same arguments plus another argument which is situated after these arguments?

我的目标是包装一个 API 函数,使包装器具有与 API 函数相同的参数,然后还有一个额外的最终参数。 API 函数非常通用,因此包装器需要从这个内部函数中获取类型和参数。

我的理由是我需要使用额外的可选参数来增强 API 函数。对于使用此包装函数的其他开发人员来说,将此可选参数作为第一个参数将是一种糟糕的体验。

我目前的尝试如下:

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]
  }
}

这几乎是我所需要的,但问题是参数 b 必须 内部函数的参数之前。

在平行宇宙中,解决方案将在新参数之前有其余参数,如下所示:

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]
  }
}

但是,由于其余参数必须是最后一个参数,因此会出错。

是否有另一种方法可以解决这个问题,使内部函数参数可以在列表中排在第一位?

在函数的参数列表中,扩展 必须 出现在其他参数之后。但是,对于元组类型,不是

这意味着您可以像这样声明 args

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

请注意,此元组的每个成员都已命名,这有助于保留有关原始参数名称的智能提示。

这确实意味着您必须自己解析 args,但这并不难:

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

像这样使用时似乎有效:

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"] 

当您将鼠标悬停在 fn 此处时,您可以看到参数名称保留在报告类型中:

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

Working playground example


One issue with this solution is that if b is an optional argument and not provided.

好吧,您可以向内部函数询问其 length,其中 returns 它接受的参数数量。

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

Playground

但是,如果内部函数有可选参数,或者像 ...args 这样展开,这显然会使事情复杂化。事实上,我认为这会让你无法知道哪些参数适用于你的内部函数,哪些应该在后面。


我可以推荐一个替代方案吗API?类似于:

fn([original, args, here], extraArg)

这样一来,就可以很容易地分辨出函数有什么用,什么是额外的。我认为再多的聪明元组类型或数组切片也无法为您提供完美的、适用于所有情况的解决方案,而无需明确地将原始参数与额外参数分开。

或者作为嵌套函数,仅在提供额外参数时才调用内部函数?

fn(original, args, here)(extraArg)