如何在包装任意函数时保留类型信息

时间:2017-06-29 17:42:38

标签: typescript generics

我正在尝试使用带有es6 promise的TypeScript实现等效于bluebird's Promise.method

所需用法:

const stringify = promiseMethod(JSON.stringify)
stringify(/* ... */) //Type checking available here, returns Promise<string>

最近的实施:

const promiseMethod = function<T, U> (fn: (T) => U) {
    if (typeof fn !== "function") {
        throw new TypeError("Parameter is not a function:" + fn);
    }
    return <(T) => Promise<U>>function () {
        try{
          var value = fn.apply(this, arguments);
          return Promise.resolve(value);
        }
        catch (error){
          return Promise.reject(error);
        }
    };
};

上述实现的问题是,当可能有许多参数时,调用站点只需要一个参数。

如果更改参数并将类型返回到Function,我可以获得可编译的代码,但是参数或返回类型没有类型信息。

1 个答案:

答案 0 :(得分:1)

没有办法以100%类型安全的方式执行此操作(AFAIK!如果我错过了某些内容,请指出。)

在一个完美的世界中,Typescript将支持使用类型参数来代表整个参数列表。所以你可以这样做:

function promiseMethod<T,R>(fn: (...args: T) => R) {

但这是不允许的。你能做的最好的是(...args: Array<any>),这是非常蹩脚的。

(有关此功能的一些讨论,请参阅github问题herehere。)

您可以采用大锤方法并重载promiseMethod函数,如下所示:

function promiseMethod<R>(fn: () => R): () => Promise<R>;
function promiseMethod<R,A>(fn: (a: A) => R): (a: A) => Promise<R>;
function promiseMethod<R,A,B>(fn: (a: A, b: B) => R): (a: A, b: B) => Promise<R>;
// etc...
function promiseMethod<R>(fn: (...args: Array<any>) => R) {
  // implementation
}

这可能会满足您的需求,但确实存在一些问题:

  • 您仍然可以使用比最大超载更多的参数调用promiseMethod,并且那种情况下的输入将会泄漏。
  • 如果fn本身有重载,也会泄漏(见下文)。

仍然,它比...args: Array<any>更好。许多常用的库(例如lodash)使用这种模式,因为没有更好的替代方案。

如果您传入的功能有重载...祝你好运。 Typescript对函数重载的支持非常浅(by design,似乎)。如果在没有调用它的情况下引用重载函数(例如将其作为回调传递给promiseMethod函数),编译器似乎只使用最后(?)定义的重载的类型签名,并抛弃其他。不是很好。当然,如果您实际传递重载函数,这只会让您感到厌烦。

最后,我的意见。除非您将大型JS代码库升级为Typescript,否则我会考虑尽可能完全避免使用promisifying模式。特别是现在完全支持async / await(因为TS 2.1),我认为没有用它。