0

我有一个类型保护检查函数,它告诉 JavaScript 一个值是否是一个承诺,同时它告诉 TypeScript 该变量是一个承诺:

function getType (payload: any): string {
  return Object.prototype.toString.call(payload).slice(8, -1)
}    

function isPromise (payload: any): payload is Promise<any> {
  return getType(payload) === 'Promise'
}

它工作得非常好,但是,我意识到我不需要硬编码 promise 解析为any而是推断该类型。

这是我看到any它不能正常工作的一个例子:

export type PlainObject = { [key: string]: any }
let a: PlainObject | PlainObject[] | Promise<PlainObject | PlainObject[]>
let b = isPromise(a) ? await a : a

在此示例中,b推断为PlainObjectwhile 它应该是PlainObject | PlainObject[]....

问题一:为什么会这样?

我尝试的更好isPromise功能的解决方案:

type Unpacked<T> =
    T extends (infer U)[] ? U :
    T extends (...args: any[]) => infer U ? U :
    T extends Promise<infer U> ? U :
    T;

function isPromise2 <T extends any>(payload: T): payload is Promise<Unpacked<T>> {
  return getType(payload) === 'Promise'
}

从理论上讲,我不明白为什么这不起作用。但我得到这个错误:

类型谓词的类型必须可分配给其参数的类型。类型“Promise>”不可分配给类型“T”。“Promise>”可分配给“T”类型的约束,但“T”可以用约束“any”的不同子类型来实例化

问题2:为什么不起作用payload is Promise<Unpacked<T>>,否则我怎么能推断出这个?

以下是 TypeScript PlayGround 中的所有内容:

https://www.typescriptlang.org/v2/en/play?#code/KYDwDg9gTgLgBDAnmYcAKAbAhgSwHYDyARgFbADG8AvHAN5wDaA1sIgFxwDOMU+A5gF0OWPIjgBfAFCSAZgFc8lHBDxw+wGABVkqABRgsiDBCwATYaICUHbrzx86kuHCga5UVcTKUAdGCgQMIE6PkEAyjz8PuRYGBj6hsZmlj6cGDjkwLoAHAA0cAC0AIyWkuLOztLyijDKqjicaAEAtg16BkYm5nAiiNZwHUmmcA3oLW0APL0AfI7OrjDuqupaOgmdyXBU23AA5E0QrZzAu2XSGBo9HJi4hKQU8AA+6Nj4Xg8MAnDPB0fAEzc3vdKN8Xrd3pRPtNJBd4EQtiNGuNjrosJY4AB+HoAd1w8CwcGE0gA9MS4NiABZYeAASRcwFiGDEuLw8CCcFMEERbEkSBQcAAqngDOQWKYJppZlQnBVNHBQDBgHhTJw4Lp8DJgFBBZZPpjBYSZc45QqlSq1T5LVgoHxOBZEJ90VRZhqtQasQLDRU4CaQIrlarfpNXdqBbMPV7ZQBuKoKJQqRFB44AJjgEvlfrNqpm6yGHE0-UGXURY0OkyFIrFEums1oMoWSzUGm0KFzXSdO32yJOZyAA

4

1 回答 1

1

1:为什么会这样?

PlainObject[]是 的子类型PlainObject在集合论中,当你有一个 union 时Subtype | Supertype,结果类型将是Supertype- 它吸收 Subtype。所以在这里b输入PlainObject

为什么是PlainObject[]亚型?当我们查看 ArrayPlainObject键入时,它变得更加清晰:

interface Array<T> { [n: number]: T} // T is PlainObject for PlainObject[]
type PlainObject = { [key: string]: any }

在 JS 中,number属性键将变为string. 不管是什么T,它都可以分配给any.

type PlainObjArr_extends_PlainObj  = PlainObject[] extends PlainObject ? true : false // true

2:为什么payload is Promise>不起作用,否则我怎么能推断出来?

对于自定义类型保护,类型谓词( is xxx) 必须是检查参数的子类型。TS 在这里无法验证,那Promise<Unpacked<T>>T. 忽略这一事实,isPromise 的工作是否会过滤掉所有不兼容的值a

let b = isPromise(a) ? await a /*Promise<PObj | PObj[]>*/ : a /*PObj | PObj[]*/

any- 通常 - 是邪恶的:o)。一个解决方案是使PlainObject[]不兼容PlainObject

export type PlainObject = { [key: string]: unknown } // e.g. replace `any` by `unknown`
let b = isPromise(a) ? await a : a  // b: PlainObject | PlainObject[]
于 2020-03-21T13:09:32.710 回答