如何键入带注释的“函数包装器”(返回与参数具有相同签名的函数的函数)

时间:2019-03-28 13:44:28

标签: flowtype

有没有一种方法可以正确地告诉流程,我要返回的函数与我传递的函数具有相同的签名,但功能不完全相同?

这是一个“一次”包装器的示例,该包装器可以防止多次调用一个函数,它可以工作,但是在内部使用一个任意转换来放弃流,我想摆脱这种转换并拥有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*/);
};

1 个答案:

答案 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。因此,如果它们具有相同的参数并返回值,则它们的类型匹配。