我具有以下功能:
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)));
};
如果您正在考虑更好的方法,请大喊大叫;)
答案 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[]]
。但是,这将是非常有限的。