In the recent version of TypeScript 4.5, there is a new type called Awaited. It looks like this type is for handling promises. I found this is not well described in the documentation and I did not find any better examples of it anywhere.
Can someone please explain what this type is for and how it works?
Thank you in advance.
CodePudding user response:
If you're looking for more detailed and explanatory documentation for the Awaited<T> utility type, you might want to look at the relevant section of the TypeScript 4.5 release notes and the implementing pull request microsoft/TypeScript#45350.
The goal of Awaited<T> is to describe the (perhaps surprisingly) complicated type operation represented by await in an async function. When you await a value of some type T, you will get a value of type Awaited<T>:
async function foo<T>(x: T) {
const y = await x;
// const y: Awaited<T> in TS4.5 and above
// (just T in TS4.4 and below)
}
If the type T of the you are awaiting isn't a Promise of any kind, then Awaited<T> is just the same as T:
async function bar(x: string) {
const y = await x;
// const y: string
// Awaited<string> is just string
}
If the type T you are awaiting is a Promise<U> where U isn't a Promise of any kind, then Awaited<T> is the same as U:
async function baz(x: Promise<string>) {
const y = await x;
// const y: string
// Awaited<Promise<string>> is just string
}
Things become more complicated when the type T you are awaiting is a Promise<Promise<V>>; if V isn't a Promise, then Awaited<T> is the same as V:
async function qux(x: Promise<Promise<string>>) {
const y = await x;
// const y: string
}
That's because await recursively unwraps any Promises until it hits a non-Promise (or some awful error condition, like a non-promise object with a then() method); you should never get a Promise out of an await:
async function quux(x: Promise<Promise<Promise<Promise<string>>>>) {
const y = await x;
// const y: string
}
So that means Awaited<T> also recursively unwraps promises, via the following recursive conditional type:
/**
* Recursively unwraps the "awaited type" of a type.
Non-promise "thenables" should resolve to `never`.
This emulates the behavior of `await`.
*/
type Awaited<T> =
T extends null | undefined ? T : // special case for `null | undefined`
// when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ?
// `await` only unwraps object types with a callable `then`.
// Non-object types are not unwrapped
F extends ((value: infer V, ...args: infer _) => any) ?
// if the argument to `then` is callable, extracts the first argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable
T; // non-object or non-thenable
It also handles union types; for example, if you have something which is either a string or a Promise<number> and await it, then you will get either a string or a number out:
async function union(x: string | Promise<number>) {
const y = await x;
// const y: string | number
}
And so on with recursively wrapped unions:
async function wha(x: number | Promise<string | Promise<number | Promise<Promise<boolean>>>>) {
const y = await x;
// const y: string | number | boolean
}
Before TypeScript 4.5 the above awaits mostly worked, but there were some glitches, especially around the behavior of Promise-combination methods like Promise.all():
async function oops(x1: Promise<string | Promise<number>>, x2: Promise<string | Promise<number>>) {
const y = await Promise.all([x1, x2]);
// const y: [string | Promise<number>, string | Promise<number>] in TS4.4 and below 