catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult> | undefined | null): Promise<T | TResult);
우선 일반적인 상황에서 (Promise T 제네릭 타입은 boolean이라고 가정) catch((e) ⇒ console.log(e))
정도로 썼다고 가정하면
TResult는 void가 되어서 onrejected는 (reason: any) ⇒ void | PromiseLike<void>
가 된다.
모아서 써보면 결국 catch(onrejected: (reason: any) ⇒ void | PromiseLike<void>): Promise<boolean | void>
가 된다.
여기에 await 키워드를 붙이면 값은 boolean | void가 된다.
물론 onrejected의 리턴값이 string이었다면 boolean | string 이 됐을 것이다.
all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T] : Awaited<T[P]> }>;
[P in keyof T] : Awaited<T[P]>
를 단순히 생각해보면
T를 쪼개서 key는 인덱스 value는 Awaited로 돌려주는 친구다.
결국 배열의 인수들이 Awaited를 타고 나오는걸 알 수 있다. Awaited는 뭘까?
type Awaited<T> =
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;
T객체가 null이나 undefined면 그냥 null | undefined
T 객체가 object ( {} ) 형태가 아니면 (i.e. string, number, boolean 등) or then~~ 함수가 없으면 그대로 T,
즉 Awaited === NotObject
T 가 객체(object) 형태이면서 then(onfulfilled: infer F, ...args: infer _): any
를 가지고 있다면.. 다음식
참고로 Promise 는 then(onfulfilled: infer F, ...args: infer _): any
를 만족함
역으로 객체에 then(onfulfilled: infer F, ...args: infer _): any
가 있으면, await 키워드를 사용할 수 있다는 말도 됨 (구조적 타이핑).
Promise.resolve('hello')
.then((e) => e);
위 예시에서 then 함수 부분이 (value: infer V, ...args: infer _) => any
이 친구가 됨
Step 2단계에서의 F는 일반적인 then 사용법을 생각해보면 그냥 단순히 함수에 불과함 (실제 Promise의 제네릭 타입이 아님)
이때, V의 값이 실제 Promise의 원본 타입이 됨.
즉, Awaited<Promise<T>> === Awaited<T>
가 됨
Awaited 는 다시 Step1을 거쳐 결국 T가 된다.