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 await
ing 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 await
ing 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 await
ing 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 Promise
s 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 await
s 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