type-challenges:Awaited

114 阅读2分钟

Awaited

问题描述

假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。

例如:Promise<ExampleType>,请你返回 ExampleType 类型。

type ExampleType = Promise<string>
​
type Result = MyAwaited<ExampleType> // string

这个挑战来自于 @maciejsikora 的文章:original article

// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }
​
type cases = [Expect<Equal<MyAwaited<X>, string>>, Expect<Equal<MyAwaited<Y>, { field: number }>>, Expect<Equal<MyAwaited<Z>, string | number>>, Expect<Equal<MyAwaited<Z1>, string | boolean>>, Expect<Equal<MyAwaited<T>, number>>]
​
// @ts-expect-error
type error = MyAwaited<number>
​
// ============= Your Code Here =============
// 答案
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer U> ? (U extends PromiseLike<any> ? MyAwaited<U> : U) : never
// 内置
type MyAwaited<T extends PromiseLike<any>> = T extends null | undefined
  ? T
  : T extends object & { then(onfulfilled: infer F, ...args: infer _): any }
  ? F extends (value: infer V, ...args: infer _) => any
    ? Awaited<V>
    : never
  : T
​
​

首先, PromiseLikePromise 都是 typescript 的内置接口(interface) , PromiseLike 只定义了 then 方法, Promise 除了定义 then 方法之外,还定义了 catch 方法,在上述的例子中我们可以看到,实际上 X, Y, Z, Z1, T 等类型最终返回的都是 "剥去" Promise 之后的类型,有点类似于 "抵消" 的意思,只要存在 MyAwaited ,那么所有的 Promise 都将被 “抵消” ,而我们实现的就是如何 “抵消”,这里观察发现,如果有多层 Promise 时,应该继续判断当前类型是否是 Promise 类型,继续走 MyAwaited ,我们先看正确答案,首先,需要约束传入的参数类型是一个 类Promise 的类型,换句话说即是只实现 then 方法的类型,当X 传入 MyAwaited 时,判断当前类型是不是 PromiseLike 类型,如果是,则使用 infer 获得 PromiseLike 类型的泛型类型 U,继续判断当前泛型类型 U 是否是 PromiseLike 类型,不是则代表 Promise 全都被 “抵消”了,是则代表当前类型 U 仍然是 PromiseLike 类型 ,需要传入到 MyAwaited 中继续 “抵消” ,最终得到最内层的类型。

我们再来看两个错误答案,这两个错误答案在测试用例 5 都不能通过,原因都是类型 T,即type T = { then: (onfulfilled: (arg: number) => any) => any } 没有实现 catch 方法,所以在判断 T extends Promise<infer U> 时会走 false 的逻辑。

lib.es5.d.ts 声明文件中的 PromiseLike 接口和 Promise 接口。

interface PromiseLike<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
​
/**
 * Represents the completion of an asynchronous operation
 */
interface Promise<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
​
    /**
     * Attaches a callback for only the rejection of the Promise.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of the callback.
     */
    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}