提取泛型的 Typescript 复杂映射类型

Typescript complex mapped types that extract generic

类似于,我有一个类型,说:

type A = {
   Item1: Promise<string>,
   Item2: Promise<number>,
   Item3: number
}

我想从中提取以下类型:

type A' = {
   Item1: string,
   Item2: number,
   Item3: number
}

请注意其中一个字段不是 Promise 的增加的复杂性。

这有可能吗,还是我只是达到了类型脚本推断类型的能力的极限?我试着摆弄映射类型和记录类型,但我就是想不通。

更新:

假设我想对函数调用做同样的事情:

type A = {
  Item1: (string, number) => Promise<string>,
  Item2: () => Promise<number>,
  Item3: () => number
}

所需的类型是:

type A' = {
  Item1: (string, number) => string,
  Item2: () => number,
  Item3: () => number
}

我认为这与我陈述的第一种情况非常相似,但函数 return 值似乎并不像我希望的那样直接。

更新 2020-01-28

自从 TypeScript 2.8 引入后 conditional types 您现在可以相对轻松地进行此映射:

type A = {
  Item1: (x: string, y: number) => Promise<string>,
  Item2: () => Promise<number>,
  Item3: () => number,
  Item4: Promise<string>,
  Item5: Promise<number>,
  Item6: number
}

type UnpromiseObj<T> = { [K in keyof T]: T[K] extends Promise<infer U> ? U :
  T[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => R :
  T[K]
}

type Aprime = UnpromiseObj<A>;
/* type Aprime = {
  Item1: (x: string, y: number) => string;
  Item2: () => number;
  Item3: () => number;
  Item4: string;
  Item5: number;
  Item6: number;
} */

我将保留以下内容,以便为后代保留在条件类型存在之前您必须经历的疯狂废话:

结束更新 2020-01-28


并非完全不可能,但如果没有对 mapped conditional types or the like 的官方支持,这是不确定的。我们试试吧。首先,让我们设置一些类型级别的布尔逻辑:

type False = '0';
type True = '1';
type Bool = False | True;
type If<Cond extends Bool, Then, Else> = { '0': Else; '1': Then }[Cond];

因此类型 If<True, Then, Else> 的计算结果为 Then,类型 If<False, Then, Else> 的计算结果为 Else.

第一个问题是您需要能够确定类型是否为 Promise。第二个是在给定 Promise<T> 的情况下,您需要能够获得 T 的类型。我将通过使用一些在运行时不存在的幻象属性来扩充 ObjectPromise<T> 接口的声明:

// if you are in a module you need to surround 
// the following section with "declare global {}"
interface Object {
  "**IsPromise**": False
}
interface Promise<T> {
  "**IsPromise**": True
  "**PromiseType**": T
}

这是不确定的部分。增加全局接口并不是很好,因为它们在每个人的命名空间中。但它具有所需的行为:任何不是 PromiseObject"**IsPromise**" 属性 和 Promise 都有一个 False 类型有一个 True 值。此外,Promise<T> 具有类型 T"**PromiseType**" 属性。同样,这些属性在运行时不存在,它们只是为了帮助编译器。

现在我们可以定义 Unpromise,它将 Promise<T> 映射到 T 并保留任何其他类型:

type Unpromise<T extends any> = If<T['**IsPromise**'], T['**PromiseType**'], T>

MapUnpromiseUnpromise 映射到对象的属性:

type MapUnpromise<T> = {
  [K in keyof T]: Unpromise<T[K]>
}

让我们看看它是否有效:

type A = {
   Item1: Promise<string>,
   Item2: Promise<number>,
   Item3: number
}
    
type Aprime = MapUnpromise<A>
// evaluates to { Item1: string; Item2: number; Item3: number; }

成功!但是我们已经对类型做了一些相当不愉快的事情来让它发生,这可能不值得。由你决定!

希望对您有所帮助;祝你好运!


更新 1

不幸的是,据我所知,不可能对函数调用做同样的事情。您确实需要 extended typeof type query 之类的东西,而目前它还不是 TypeScript 的一部分(无论如何从 TypeScript v2.5 开始)。

所以你不能使用你的类型A并从中计算出APrime(注意A'不是一个有效的标识符。如果你使用想)。但是你可以创建一个基本类型,你可以从中计算 AAPrime:

type False = '0';
type True = '1';
type Bool = False | True;
type If<Cond extends Bool, Then, Else> = { '0': Else; '1': Then }[Cond];
type MaybePromise<Cond extends Bool, T> = If<Cond, Promise<T>, T>

我放弃了全局增强并添加了 MaybePromise<Cond, T>,其中 MaybePromise<True, T> 计算为 Promise<T>MaybePromise<False, T> 计算为 T。现在我们可以使用 MaybePromise<>:

得到 AAPrime
type ABase<Cond extends Bool> = {
  Item1: (s: string, n: number) => MaybePromise<Cond, string>,
  Item2: () => MaybePromise<Cond, number>,
  Item3: () => number
}

type A = ABase<True>;
// evaluates to { 
//   Item1: (s: string, n: number) => Promise<string>; 
//   Item2: () => Promise<number>; 
//   Item3: () => number; }


type APrime = ABase<False>;
// evaluates to { 
//   Item1: (s: string, n: number) => string; 
//   Item2: () => number; 
//   Item3: () => number; }

所以这行得通!但是我建议的重构可能不适合您的用例。这首先取决于您如何获取 A 类型。哦,那是我能做的最好的。希望它有一些帮助。再次祝你好运!

现在使用 TypeScript 4.5 的 Awaited 实用程序类型变得更容易了:

type A = {
  Item1: Promise<string>;
  Item2: Promise<number>;
  Item3: number;
  Item4: (arg0: string, arg1: number) => Promise<string>;
  Item5: () => Promise<number>;
  Item6: () => number;
};

type AwaitValues<T> = {
  [K in keyof T]: T[K] extends (...args: infer P) => infer R
    ? (...args: P) => Awaited<R>
    : Awaited<T[K]>;
};

type A2 = AwaitValues<A>;

通过悬停 A2 我们可以看到它的类型是:

/*
type A2 = {
    Item1: string;
    Item2: number;
    Item3: number;
    Item4: (arg0: string, arg1: number) => string;
    Item5: () => number;
    Item6: () => number;
}
*/