TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
182: Awaited<Type>
Imagine you've ordered a high-end espresso machine online. When you hit "Buy," the store doesn't immediately hand you the machine; they hand you a tracking number. That tracking number is a promise that, eventually, you'll get your machine. But sometimes, things get weird. The tracking number leads you to a shipping coordinator, who then gives you a *different* tracking number from a local courier, who finally delivers the actual box to your door.
In your head, you aren't thinking, "I possess a promise of a promise of an espresso machine." You're thinking, "I'm getting an espresso machine." You've mentally stripped away all the "delivery" layers to focus on the actual object at the center.
That is exactly what Awaited<T> does for your types. While the await keyword unwraps a promise at runtime, the Awaited utility type unwraps a promise at compile-time.
Unwrapping the Promise Onion
If you have a simple promise, Awaited is pretty straightforward. It looks at the type and says, "If this is a promise, give me what's inside. If it's not a promise, just leave it alone."
type Delivery = Promise<string>;
type Result = Awaited<Delivery>; // Result is now just 'string'
type SimpleValue = number;
type StillSimple = Awaited<SimpleValue>; // StillSimple is still 'number'
Now, you might be thinking, "Why not just use Promise<string> and access the generic?" Because real-world APIs are messy. Sometimes you encounter "thenables" or nested promises (promises that resolve to other promises). Awaited is recursive. It will keep digging through the layers until it hits a non-promise value.
Handling Recursive Nesting
I've seen this happen in legacy codebases where a wrapper function accidentally wraps a result in multiple promises. If you tried to manually define the return type, you'd be writing Promise<Promise<Promise<User>>>, which is a nightmare to maintain. Awaited flattens all of that instantly.
type DeepPromise = Promise<Promise<Promise<string>>>;
// This recursively unwraps everything
type FinalValue = Awaited<DeepPromise>; // FinalValue is 'string'
Connecting Awaited to Function Return Types
The most common place you'll actually use this is when you're working with generic functions and you want to create a second function that handles the result of the first one. You don't want to hardcode the return type because the first function might change.
Check this out:
async function fetchUserConfig() {
// Imagine some complex logic here
return { theme: 'dark', notifications: true };
}
// We use 'ReturnType' to get the Promise, then 'Awaited' to get the actual object
type UserConfig = Awaited<ReturnType<typeof fetchUserConfig>>;
function applyConfig(config: UserConfig) {
console.log(`Setting theme to ${config.theme}`);
}
By chaining Awaited and ReturnType, your applyConfig function is now automatically synced with whatever fetchUserConfig returns. If you add a language property to the config in the fetcher, UserConfig updates automatically. I love this pattern because it eliminates the need to manually maintain duplicate interface definitions for API responses.
📋 Practical Task
Synchronizing a Cached API Response Type
You are building a caching layer for a weather application. You have a function that fetches weather data, which might be returned from a cache (already resolved) or an API (a promise). You need to create a WeatherDisplay component that accepts the unwrapped data type.
Your Task:
- Given the function
getWeatherData, useReturnTypeandAwaitedto create a type calledWeatherData. - Apply that
WeatherDatatype to thedataparameter in therenderWeatherfunction.
async function getWeatherData() {
return {
temp: 72,
condition: 'Sunny',
humidity: 45
};
}
// 1. Extract the unwrapped return type here
type WeatherData = // Your code here
function renderWeather(data: WeatherData) {
console.log(`The weather is ${data.condition} at ${data.temp} degrees.`);
}
There are no comments for now.