鸭子在打字稿中键入一个承诺

时间:2019-03-14 15:49:32

标签: typescript generics

假设我有一个日志记录函数,该函数接受一个函数并记录名称,参数和结果:

function log<A extends any[], B>(f: (...args: A) => B): (...args: A) => B {
    return function (...args: A): B {
        console.log(f.name);
        console.log(JSON.stringify(args));
        const result = f(...args);
        console.log(result);
        return result;
    }
}

这有效,并且AFAICT保留了传入函数的类型安全性。但这会中断,如果我想为Promises添加特殊处理:

function log<A extends any[], B>(f: (...args: A) => B) {
    return function (...args: A): B {
        console.log(f.name);
        console.log(JSON.stringify(args));
        const result = f(...args);
        if (result && typeof result.then === 'function') {
            result.then(console.log).catch(console.error);
        } else {
            console.log(result);
        }
        return result;
    }
}

在这里,编译器抱怨.then在类型B上不存在。所以我可以转换为Promise:

if (typeof (<Promise<any>>result).then === 'function') {

这也不起作用,它是比通用类型更具体的类型。错误消息建议转换为未知:

const result: unknown = f(...args);

但是现在返回的类型与HOF的返回签名不匹配,并且编译器自然不允许这样做。

现在,我可以使用instanceof Promise的支票了:

if (result instanceof Promise) {
  result.then(console.log).catch(console.error);

并且编译器很高兴。但是,这并不理想:我更愿意对所有可能的模型进行通用测试,而不仅仅是对本地Promise构造函数进行通用测试(更不用说像Promise构造函数之类的奇怪场景来自不同窗口)了。我也希望这是一个功能,而不是两个(或更多!)。实际上,使用此检查来确定对象上是否存在方法是一种非常常见的Javascript习惯用法。

如何在保留原始函数参数的返回类型的同时执行此操作?

1 个答案:

答案 0 :(得分:2)

  

我更愿意对所有可能的模型进行通用测试,而不仅仅是对本地Promise构造函数进行测试

这可能满足您的要求:

if (result && typeof (result as any).then === 'function') {
  (result as any).then(console.log).catch(console.error);
} else {
  console.log(result);
}

如果这样做,则可以将其into a user-defined type guard分解:

const isThenable = (input: any): input is Promise<any> => { 
  return input && typeof input.then === 'function';
}

使用用户定义的类型防护,log函数将看起来像这样(here it is in the TypeScript playground):

const isThenable = (input: any): input is Promise<any> => { 
  return input && typeof input.then === 'function';
}

function log<A extends any[], B>(f: (...args: A) => B) {
  return function (...args: A): B {
    console.log(f.name);
    console.log(JSON.stringify(args));
    const result = f(...args);
    if (isThenable(result)) {
      result.then(console.log).catch(console.error);
    } else {
      console.log(result);
    }
    return result;
  }
}