Как вывести правильный тип при возврате `payload is T` в машинописи? - PullRequest
0 голосов
/ 21 марта 2020

У меня есть функция проверки типа, которая сообщает 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'
}

Это работает очень хорошо, однако, я понял, что мне нужно не жестко кодировать обещание разрешить до 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'
}

Теоретически я не понимаю, почему это не сработает. Но я получаю эту ошибку:

Тип предиката типа должен быть назначен типу его параметра. Тип «Обещание>» нельзя назначить типу «T». «Обещание>» присваивается ограничению типа «T», но экземпляр «T» может быть создан с другим подтипом ограничения «любой»

Вопрос 2: Почему не payload is Promise<Unpacked<T>> работа и как я могу сделать вывод, это в противном случае

1034 * Здесь есть все выше в машинописи площад: 1036 *https://www.typescriptlang.org/v2/en/play?#code / KYDwDg9gTgLgBDAnmYcAKAbAhgSwHYDyARgFbADG8AvHAN5wDaA1sIgFxwDOMU + A5gF0OWPIjgBfAFCSAZgFc8lHBDxw + wGABVkqABRgsiDBCwATYaICUHbrzx86kuHCga5UVcTKUAdGCgQMIE6PkEAyjz8PuRYGBj6hsZmlj6cGDjkwLoAHAA0cAC0AIyWkuLOztLyijDKqjicaAEAtg16BkYm5nAiiNZwHUmmcA3oLW0APL0AfI7OrjDuqupaOgmdyXBU23AA5E0QrZzAu2XSGBo9HJi4hKQU8AA + 6Nj4Xg8MAnDPB0fAEzc3vdKN8Xrd3pRPtNJBd4EQtiNGuNjrosJY4AB + HoAd1w8CwcGE0gA9MS4NiABZYeAASRcwFiGDEuLw8CCcFMEERbEkSBQcAAqngDOQWKYJppZlQnBVNHBQDBgHhTJw4Lp8DJgFBBZZPpjBYSZc45QqlSq1T5LVgoHxOBZEJ90VRZhqtQasQLDRU4CaQIrlarfpNXdqBbMPV7ZQBuKoKJQqRFB44AJjgEvlfrNqpm6yGHE0-UGXURY0OkyFIrFEums1oMoWSzUGm0KFzXSdO32yJOZyAA

1 Ответ

1 голос
/ 21 марта 2020

1: Почему это происходит?

PlainObject[] является подтипом из PlainObject. В теории множеств, когда у вас есть объединение Subtype | Supertype, тогда результирующий тип будет Supertype - он поглощает 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: Почему полезная нагрузка не работает, как 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[]
...