有没有办法正确地告诉我我正在返回一个与我传递的函数具有相同签名的函数,但不是完全相同的函数?
这是一个“一次”包装器的例子,它阻止一个函数被多次调用,它可以工作但是在内部使用任意转换来让流程放弃,我想摆脱那个演员并且有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*/);
};
好的,首先要做的事情。
你的返回值目前永远不会匹配F
而没有通过any
进行投射,因为你返回的函数的签名是不一样的,因为它可以返回undefined
,而原始可能没有。
(为了便于阅读,删除了注释语法)
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
,因为它是generally better if we don't:
但是,如果您需要选择退出类型检查程序,并且不想一直转到任何类型检查程序,则可以改为使用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
?我们怎么能总是从我们的onced函数返回相同的类型?好吧,一个选项是将单个调用的返回值存储到函数中,并始终为后续调用返回存储的返回值。这有点意义,因为整点是允许多次调用但不执行任何函数效果。所以这样我们就可以避免改变函数的接口,所以我们真的不需要知道函数之前是否被调用过。
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
): ((...Array<Args>) => Return) => {
let guard = false;
let returnValue: Return;
return function () {
if (guard) { return returnValue; }
guard = true;
returnValue = f.apply(null, arguments);
return returnValue;
};
};
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once2(func);
在这一点上要问的一个很好的问题是,即使我们在技术上没有完全返回F
,为什么类型匹配?答案是因为functions in flow are structurally typed。因此,如果它们具有相同的参数和返回值,则它们的类型匹配。