有没有一种方法可以正确地告诉流程,我要返回的函数与我传递的函数具有相同的签名,但功能不完全相同?
这是一个“一次”包装器的示例,该包装器可以防止多次调用一个函数,它可以工作,但是在内部使用一个任意转换来放弃流,我想摆脱这种转换并拥有100%覆盖率:
module.exports.once = /*::<F:Function>*/(f /*:F*/) /*:F*/ => {
let guard = false;
return ((function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
}/*:any*/) /*:F*/);
};
答案 0 :(得分:1)
好的,首先要做的事情。
如果不通过F
进行强制转换,您的返回值将永远无法匹配any
,因为您要返回的函数的签名不是 ,因为它可以返回{ {1}},原件可能没有。
(删除注释语法以提高可读性)
undefined
但是要开始输入此内容,我们将需要对该泛型函数进行一些细分。
首先,我们不要使用https://i.stack.imgur.com/w6ATB.jpg,因为它是generally better if we don't:
但是,如果您需要退出类型检查器,并且不想一路走下去,可以改用Function。 功能不安全,应避免使用。
此外,我们将提取参数的类型和返回值,以便我们可以独立地操作它们并构造返回类型。我们将它们称为module.exports.once = <F: Function>(f: F): F => {
let guard = false;
return ((function () { // this function returns the return value of F or void
if (guard) { return; } // returning void
guard = true;
return f.apply(null, arguments);
}: any): F);
};
和Function
,以便于跟踪。
Args
现在,我们考虑到我们的新函数可能会返回Return
,所有类型都检查正常。但是,当然,我们的module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
) ((...Array<Args>) => Return | void) => { // note `Return | void`
let guard = false;
return function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
};
};
函数的返回类型将不再与传递的函数的类型匹配。
void
有道理吧?
因此,让我们讨论此功能的签名。我们希望我们的返回值具有相同的签名作为我们传入的函数。当前不是因为我们要在签名中添加once
。我们需要吗?为什么我们返回type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once(func); // error!
// Cannot assign `module.exports.once(...)` to `onceFunc` because
// undefined [1] is incompatible with string [2] in the return value.
?我们如何总是从我们的函数中返回相同的类型?好吧,一种选择是存储从单个调用到函数的返回值,并总是返回存储的返回值以用于后续调用。这是有道理的,因为重点是允许多次调用而不执行任何功能效果。这样一来,我们就可以避免更改函数的接口,因此我们真的不需要知道函数是否已被调用过。
void
这时要问的一个好问题是,即使我们不是技术上完全返回undefined
,为什么类型仍然匹配?答案是因为functions in flow are structurally typed。因此,如果它们具有相同的参数并返回值,则它们的类型匹配。