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.
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
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 ðŸ˜
// const y: [string | number, string | number] in TS4.5 and above 😊
}
So the Awaited<T>
type was introduced in order to more fully handle the effects of Promise
manipulation, in the face of some weird edge cases that people had reported as bugs (which you can see in microsoft/TypeScript#45350).
In practice you probably don't need to deal with it much yourself, but it's helpful that it exists.