0189 · Awaited
0189 · Awaited
Problem
Implement a generic MyAwaited<T> that recursively unwraps the value inside a Promise.
type Example1 = MyAwaited<Promise<string>> // string
type Example2 = MyAwaited<Promise<Promise<number>>> // numberSolution
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer U>
? U extends PromiseLike<any>
? MyAwaited<U>
: U
: neverExplanation
The core idea is to repeatedly extract the resolved value type until the result is no longer promise-like.
Step by Step
T extends PromiseLike<any>ensures the input is promise-like.T extends PromiseLike<infer U>extracts the value inside the promise.- If
Uis stillPromiseLike<any>, recurse withMyAwaited<U>. - Otherwise, return
U.
Why PromiseLike?
PromiseLike<T> is more general than Promise<T>. It matches standard promises and thenable-style objects, which is exactly what the challenge expects.
Example Walkthrough
type Result = MyAwaited<Promise<Promise<number>>>This expands as:
MyAwaited<Promise<Promise<number>>>
-> MyAwaited<Promise<number>>
-> numberAlternative Solutions
Option 1: Single-Branch Recursive Form
type MyAwaited2<T extends PromiseLike<any>> = T extends PromiseLike<infer U>
? U extends PromiseLike<any>
? MyAwaited2<U>
: U
: neverThis is effectively the same logic with a different name. It is explicit and easy to read.
Option 2: Recursive Unwrap Helper
type UnwrapPromise<T> = T extends PromiseLike<infer U> ? UnwrapPromise<U> : T
type MyAwaited3<T extends PromiseLike<any>> = UnwrapPromise<T>This separates "recursive unwrapping" from the public challenge type. It is useful when the same unwrapping logic will be reused elsewhere.
Thought Process
The challenge looks simple if we only consider Promise<number>, but the recursive case is the real point. A one-level unwrap is not enough because Promise<Promise<T>> should still produce T.
That naturally leads to a conditional type plus infer:
- Use
inferto grab the resolved value. - Check whether that value is still promise-like.
- Keep unwrapping until it is not.
Key concepts:
中文解析
解题思路
// T extends PromiseLike<any>:约束入参必须是类 Promise 对象
// T extends PromiseLike<infer U>:用 infer 推导出 Promise 内部的类型 U
// 若 U 仍是 PromiseLike,则递归拆包;否则 U 就是最终结果
type MyAwaited<T extends PromiseLike<any>> =
T extends PromiseLike<infer U> // 第一层:提取 Promise 内部类型 U
? U extends PromiseLike<any> // 第二层:U 是否还是 Promise?
? MyAwaited<U> // 是 → 递归继续拆包
: U // 否 → U 就是最终值类型
: never // 理论上不可达(T 已被约束为 PromiseLike)逐步分析
第一步:用 infer 提取 Promise 内部类型
infer 关键字用于在条件类型的 extends 子句中声明一个待推导的类型变量:
type Unwrap<T> = T extends Promise<infer U> ? U : never
// Unwrap<Promise<string>> → string
// Unwrap<Promise<number>> → number
// Unwrap<string> → never(不匹配则走 false 分支)第二步:处理嵌套 Promise(递归的必要性)
一次 infer 只能剥开一层,但题目要求深度拆包:
// 单层 unwrap 不够:
type BadAwaited<T> = T extends Promise<infer U> ? U : never
// BadAwaited<Promise<Promise<number>>>
// → Promise<number>(只剥了一层,还是 Promise)
// 正确做法:检查 U 是否还是 Promise,是则递归
type MyAwaited<T extends PromiseLike<any>> =
T extends PromiseLike<infer U>
? U extends PromiseLike<any> ? MyAwaited<U> : U
: never
// MyAwaited<Promise<Promise<number>>>
// → MyAwaited<Promise<number>> (U = Promise<number>,继续递归)
// → number (U = number,不是 PromiseLike,返回)第三步:为什么用 PromiseLike 而非 Promise
PromiseLike<T> 是只有 .then() 方法的最小接口,而 Promise<T> 还要求 .catch()、.finally() 等方法。题目测试用例包含自定义的 thenable 对象,因此使用更宽泛的 PromiseLike 能正确处理这些边缘情况:
// PromiseLike 的接口定义(TypeScript 标准库中):
interface PromiseLike<T> {
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ...,
onrejected?: ...
): PromiseLike<TResult1 | TResult2>
}
// 任何有 .then() 方法的对象都满足 PromiseLike考察知识点
infer关键字:在条件类型的extends子句中声明待推导类型变量,是实现类型"解构"的核心工具。理解infer是解题的关键,它让我们在类型层面"打开"泛型容器,取出内部类型。递归条件类型(Recursive Conditional Types):TypeScript 4.1+ 支持类型别名的递归定义。对于需要深度遍历的类型结构(嵌套 Promise、嵌套数组等),递归条件类型是标准解法。
PromiseLikevsPromise的区别:PromiseLike是结构化类型(structural typing)的最小接口,比Promise更宽泛,能匹配所有 thenable 对象。在处理异步类型时优先考虑PromiseLike可以提升类型的兼容性。条件类型的 false 分支:本题中
? MyAwaited<U> : U : never的最外层never理论上不可达(因为T已被约束为PromiseLike),但 TypeScript 要求条件类型必须有完整的两个分支,never在此作为占位符,同时也是语义上合理的选择("不可能到达的类型")。
