A promise’s fulfillment type and its rejection behavior are different contracts. Built-in Awaited<T> describes the value obtained by awaiting T. ReturnType<F> describes what a function returns. Combining them lets us name a loader’s fulfilled data without repeating its annotation.
Use Awaited<ReturnType<typeof load>> on a known function, or constrain a helper to zero-argument functions when that is the API you actually support. Do not use any merely to make a type extractor accept every possible function shape. A small precise contract is easier to check.
A transport returning Promise<unknown> still needs decoding after await. Awaited cannot turn that unknown into a trusted event. Nor does a Promise<Result<T>> promise that the function never rejects: a thrown programming error can still reject it. Catch only the failures you deliberately convert.
The final event gateway dispatches synchronously. An optional async transport adapter could feed it validated messages, but async listener coordination would need a different API. Keep these lifecycles distinct rather than assuming a Promise annotation solves them all.
Predict, check, and run
Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.
async function load() {
return { title: "Types", pages: 120 };
}
type Loaded = Awaited<ReturnType<typeof load>>;
const book: Loaded = await load();
console.log(book.title, book.pages);Enable JavaScript to edit and type-check this example.
Check your prediction
Types 120 Read the compiler error
Predict the rejected relationship before checking. Read the real diagnostic and its source location; fix the contract or the value instead of hiding it with an assertion.
async function loadCount(): Promise<number> {
return 0;
}
type Count = Awaited<ReturnType<typeof loadCount>>;
const count: Count = loadCount();
console.log(count);Enable JavaScript to edit and type-check this example.
Check your diagnosis
Count is the awaited number, but calling an async function produces a promise. Await the call before assigning.
Awaited describes fulfillment rather than rejection policy
ReturnType derives what a function returns; Awaited derives what awaiting that return makes available. Their composition is useful for a known loader, but it cannot turn unknown transport data into validated domain data.
Promise<Result<T>> also does not guarantee the function cannot reject. An expected failure may fulfill as a Result branch, while a programming defect can still throw. Test these paths separately instead of relying on one annotation to encode every failure policy.
async function load() {
return { title: "Types", pages: 120 };
}
type Loaded = Awaited<ReturnType<typeof load>>;
const book: Loaded = await load();
console.log(book.title, book.pages);Enable JavaScript to edit and type-check this example.
Check your prediction
Types 120 The alias tracks the loader’s fulfilled shape. Removing await from the assignment must fail; deriving a type does not make a future value available immediately.
type Result<T> =
| {
ok: true;
value: T;
}
| {
ok: false;
message: string;
};
async function load(): Promise<Result<number>> {
throw new Error("defect");
}
try {
await load();
} catch (error) {
console.log(error instanceof Error ? error.message : "unknown");
}Enable JavaScript to edit and type-check this example.
Check your prediction
defect The function still has a declared fulfillment type, even though this implementation rejects. A caller must understand which failures the implementation deliberately converts and which it propagates.
Try the rule on new inputs
Write one fulfilled Result failure and one rejected promise. Identify which is handled by narrowing and which by catch. Then derive a Promise<unknown> loader’s result and explain why a decoder is still needed.
Build it yourself
Awaited & Async Boundaries: practise
Write an async loadCount returning zero, derive Count with Awaited and ReturnType, and use it for a variable. Await loadCount and print 0. Explain why the alias says nothing about rejection causes.
// Start from the stated contract. Add a valid call and an edge case.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
ReturnType gives Promise<number>; Awaited obtains number from that promise type.
Hint 2: a first step
Use typeof loadCount when supplying a function’s type to ReturnType.
Show one solution
async function loadCount(): Promise<number> {
return 0;
}
type Count = Awaited<ReturnType<typeof loadCount>>;
const count: Count = await loadCount();
console.log(count);Enable JavaScript to edit and type-check this example.
0Trace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.
Change it and test it
Change loadCount to throw an Error and observe that its declared fulfillment type need not change. Then make a transport Promise<unknown> and explain why a decoder is still required.
Completion is saved in this browser only. It does not measure mastery.