在打字稿中返回`payload is T`时如何推断正确的类型?
How to infer correct type when returning `payload is T` in typescript?
我有一个类型保护检查函数,它告诉 JavaScript 一个值是否是一个 promise,同时它告诉 TypeScript 该变量是一个 promise:
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'
}
它工作得很好,但是,我意识到我不需要将承诺解析硬编码为 any
,而是推断该类型。
这是我看到 any
无法正常工作的一个例子:
export type PlainObject = { [key: string]: any }
let a: PlainObject | PlainObject[] | Promise<PlainObject | PlainObject[]>
let b = isPromise(a) ? await a : a
在这个例子中,b
被推断为 PlainObject
而它应该是 PlainObject | PlainObject[]
...
问题 1:为什么会这样?
我在更好的 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'
}
理论上我不明白为什么这行不通。但是我得到这个错误:
A type predicate's type must be assignable to its parameter's type.
Type 'Promise>' is not assignable to type 'T'.
'Promise>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'any'
问题 2: 为什么 payload is Promise<Unpacked<T>>
不起作用并且
否则我怎么能推断出这个?
以上是 TypeScript PlayGround 中的所有内容:
1: Why is this happening?
PlainObject[]
是 PlainObject
的 子类型 。在集合论中,当你有联合 Subtype | Supertype
时,结果类型将是 Supertype
- 它 absorbs Subtype
。所以 b
在此处获取类型 PlainObject
。
为什么 PlainObject[]
是子类型?当我们查看 Array
和 PlainObject
类型时,它会变得更清楚:
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: Why doesn't payload is Promise> work and how could I infer this otherwise?
对于自定义类型保护,type predicate (is xxx
) 必须是已检查参数的 子类型 。 TS 无法在这里验证 Promise<Unpacked<T>>
是 T
的子类型。忽略这个事实,isPromise
确实 它的工作是过滤掉 a
:
的所有不兼容值
let b = isPromise(a) ? await a /*Promise<PObj | PObj[]>*/ : a /*PObj | PObj[]*/
any
- 通常 - 是邪恶的 :o)。 solution 是使 PlainObject[]
与 PlainObject
不兼容:
export type PlainObject = { [key: string]: unknown } // e.g. replace `any` by `unknown`
let b = isPromise(a) ? await a : a // b: PlainObject | PlainObject[]
我有一个类型保护检查函数,它告诉 JavaScript 一个值是否是一个 promise,同时它告诉 TypeScript 该变量是一个 promise:
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'
}
它工作得很好,但是,我意识到我不需要将承诺解析硬编码为 any
,而是推断该类型。
这是我看到 any
无法正常工作的一个例子:
export type PlainObject = { [key: string]: any }
let a: PlainObject | PlainObject[] | Promise<PlainObject | PlainObject[]>
let b = isPromise(a) ? await a : a
在这个例子中,b
被推断为 PlainObject
而它应该是 PlainObject | PlainObject[]
...
问题 1:为什么会这样?
我在更好的 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'
}
理论上我不明白为什么这行不通。但是我得到这个错误:
A type predicate's type must be assignable to its parameter's type. Type 'Promise>' is not assignable to type 'T'. 'Promise>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'any'
问题 2: 为什么 payload is Promise<Unpacked<T>>
不起作用并且
否则我怎么能推断出这个?
以上是 TypeScript PlayGround 中的所有内容:
1: Why is this happening?
PlainObject[]
是 PlainObject
的 子类型 。在集合论中,当你有联合 Subtype | Supertype
时,结果类型将是 Supertype
- 它 absorbs Subtype
。所以 b
在此处获取类型 PlainObject
。
为什么 PlainObject[]
是子类型?当我们查看 Array
和 PlainObject
类型时,它会变得更清楚:
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: Why doesn't payload is Promise> work and how could I infer this otherwise?
对于自定义类型保护,type predicate (is xxx
) 必须是已检查参数的 子类型 。 TS 无法在这里验证 Promise<Unpacked<T>>
是 T
的子类型。忽略这个事实,isPromise
确实 它的工作是过滤掉 a
:
let b = isPromise(a) ? await a /*Promise<PObj | PObj[]>*/ : a /*PObj | PObj[]*/
any
- 通常 - 是邪恶的 :o)。 solution 是使 PlainObject[]
与 PlainObject
不兼容:
export type PlainObject = { [key: string]: unknown } // e.g. replace `any` by `unknown`
let b = isPromise(a) ? await a : a // b: PlainObject | PlainObject[]