SyntaxStudy
Sign Up
TypeScript Awaited, Template Literal Utilities, and Custom Utilities
TypeScript Beginner 1 min read

Awaited, Template Literal Utilities, and Custom Utilities

Awaited, introduced in TypeScript 4.5, recursively unwraps Promise types. Unlike the older ReturnType approach, Awaited correctly handles nested promises and thenable objects. It is the correct type to use when you want the resolved value type of any promise-like expression. The string manipulation utility types — Uppercase, Lowercase, Capitalize, and Uncapitalize — transform string literal types at the type level. They are intrinsic types implemented in the compiler itself rather than as user-definable mapped types, but they combine naturally with template literal types to build sophisticated string type transformations. Beyond the built-in utilities, you can compose your own utility types by combining mapped types, conditional types, and the built-in utilities. A well-designed library of custom utility types can dramatically reduce type duplication and make complex type annotations readable. Common custom utilities include DeepPartial, DeepReadonly, Flatten, Prettify, and various forms of type-safe event bus or state machine types.
Example
// Awaited — unwrap nested promises
type P1 = Awaited<Promise<string>>;              // string
type P2 = Awaited<Promise<Promise<number>>>;     // number
type P3 = Awaited<string | Promise<boolean>>;    // string | boolean

async function fetchData(): Promise<{ id: number; name: string }> {
    return { id: 1, name: "Alice" };
}
type FetchedData = Awaited<ReturnType<typeof fetchData>>;
// { id: number; name: string }

// String manipulation utility types
type EventName = "click" | "focus" | "mouseenter";
type UpperEvents = Uppercase<EventName>;   // "CLICK" | "FOCUS" | "MOUSEENTER"
type Handlers    = `on${Capitalize<EventName>}`;  // "onClick" | "onFocus" | "onMouseenter"

// Custom utility: Prettify — flatten intersection types for readability
type Prettify<T> = { [K in keyof T]: T[K] } & {};

type A = { name: string } & { age: number } & { id: number };
type PrettyA = Prettify<A>; // { name: string; age: number; id: number }

// Custom utility: Optional — make specific keys optional
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface StrictUser { id: number; name: string; bio: string; avatar: string }
type FlexibleUser = Optional<StrictUser, "bio" | "avatar">;
// { id: number; name: string; bio?: string; avatar?: string }

// Custom utility: ReadonlyDeep
type ReadonlyDeep<T> = {
    readonly [K in keyof T]: T[K] extends object ? ReadonlyDeep<T[K]> : T[K];
};