35. Awaited, PromiseType
TypeScript: Броня. Урок 34: Awaited и Promise Types
Заголовок раздела «TypeScript: Броня. Урок 34: Awaited и Promise Types»Awaited - это встроенный utility type (TypeScript 4.5+), который рекурсивно unwraps (распаковывает) Promise типы. Это критически важно для работы с async/await и позволяет правильно выводить типы для вложенных Promise.
Awaited<T>
Заголовок раздела «Awaited<T>»Awaited<T> извлекает тип из Promise, рекурсивно обрабатывая вложенные Promise:
// Определение Awaited (упрощённое)type Awaited<T> = T extends null | undefined ? T : T extends object & { then(onfulfilled: infer F): any } ? F extends (value: infer V, ...args: any) => any ? Awaited<V> : never : T;
// Простые примерыtype A = Awaited<Promise<string>>; // stringtype B = Awaited<Promise<Promise<number>>>; // numbertype C = Awaited<string>; // string (не Promise)
// Глубокая вложенностьtype DeepPromise = Promise<Promise<Promise<number>>>;type Unwrapped = Awaited<DeepPromise>; // number
// С union типамиtype MaybePromise = Promise<string> | number;type Result = Awaited<MaybePromise>; // string | numberAwaited + ReturnType
Заголовок раздела «Awaited + ReturnType»// Получение типа результата async функцииasync function fetchUser() { return { id: '123', name: 'Alice', };}
// Без Awaited - получаем Promisetype FetchUserReturn = ReturnType<typeof fetchUser>;// Promise<{ id: string; name: string; email: string }>
// С Awaited - получаем unwrapped типtype User = Awaited<ReturnType<typeof fetchUser>>;// { id: string; name: string; email: string }
// Короткая версия для async функцийtype AsyncReturnType<T extends (...args: any) => Promise<any>> = Awaited<ReturnType<T>>;
type UserType = AsyncReturnType<typeof fetchUser>;// { id: string; name: string; email: string }Promise<T>
Заголовок раздела «Promise<T>»Promise<T> - встроенный тип для Promise объектов:
// Создание typed Promisefunction delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms));}
function fetchData(url: string): Promise<Response> { return fetch(url);}
async function getData<T>(url: string): Promise<T> { const response = await fetch(url); return response.json();}
// Использованиеconst data: Promise<User> = getData<User>('/api/user');const user: User = await data;Практический пример: Type-safe API Client
Заголовок раздела «Практический пример: Type-safe API Client»// API client с правильной типизацией Promiseinterface ApiResponse<T> { data: T; status: number; message: string;}
class ApiClient { private baseUrl: string;
constructor(baseUrl: string) { this.baseUrl = baseUrl; }
async get<T>(endpoint: string): Promise<ApiResponse<T>> { const response = await fetch(`${this.baseUrl}${endpoint}`); const data = await response.json();
return { data, status: response.status, message: 'Success', }; }
async post<T, D = any>( endpoint: string, body: D ): Promise<ApiResponse<T>> { const response = await fetch(`${this.baseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), });
const data = await response.json();
return { data, status: response.status, message: 'Created', }; }}
// Использованиеconst api = new ApiClient('https://api.example.com');
// Promise<ApiResponse<User>>const userPromise = api.get<User>('/users/123');
// ApiResponse<User>const userResponse = await userPromise;
// Userconst user = userResponse.data;
// Или с Awaitedtype UserResponseType = Awaited<typeof userPromise>;// ApiResponse<User>
type UserDataType = Awaited<typeof userPromise>['data'];// UserPromise.all и Awaited
Заголовок раздела «Promise.all и Awaited»// Type-safe Promise.allasync function fetchMultiple() { const [users, posts, comments] = await Promise.all([ fetch('/api/users').then(r => r.json()) as Promise<User[]>, fetch('/api/posts').then(r => r.json()) as Promise<Post[]>, fetch('/api/comments').then(r => r.json()) as Promise<Comment[]>, ]);
return { users, posts, comments };}
type MultipleData = Awaited<ReturnType<typeof fetchMultiple>>;// { users: User[]; posts: Post[]; comments: Comment[] }
// Generic Promise.all wrappertype PromiseValues<T extends readonly unknown[]> = { [K in keyof T]: Awaited<T[K]>;};
async function parallelFetch<T extends readonly Promise<any>[]>( ...promises: T): Promise<PromiseValues<T>> { const results = await Promise.all(promises); return results as PromiseValues<T>;}
// Использованиеconst results = await parallelFetch( api.get<User>('/users/1'), api.get<Post>('/posts/1'), api.get<Comment>('/comments/1'));
// results: [ApiResponse<User>, ApiResponse<Post>, ApiResponse<Comment>]const [userRes, postRes, commentRes] = results;Жизненный пример: Async State Management
Заголовок раздела «Жизненный пример: Async State Management»// Type-safe async statetype AsyncState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: string };
// Helper для создания async state из Promiseasync function fromPromise<T>( promise: Promise<T>): Promise<AsyncState<Awaited<T>>> { try { const data = await promise; return { status: 'success', data }; } catch (error) { return { status: 'error', error: error instanceof Error ? error.message : 'Unknown error', }; }}
// React hook примерclass AsyncStateManager<T> { private state: AsyncState<T> = { status: 'idle' }; private listeners: Set<(state: AsyncState<T>) => void> = new Set();
getState(): AsyncState<T> { return this.state; }
subscribe(listener: (state: AsyncState<T>) => void): () => void { this.listeners.add(listener); return () => this.listeners.delete(listener); }
async execute(promise: Promise<T>): Promise<void> { this.setState({ status: 'loading' });
try { const data = await promise; this.setState({ status: 'success', data }); } catch (error) { this.setState({ status: 'error', error: error instanceof Error ? error.message : 'Unknown error', }); } }
private setState(state: AsyncState<T>): void { this.state = state; this.listeners.forEach(listener => listener(state)); }}
// Использованиеconst userManager = new AsyncStateManager<User>();
userManager.subscribe(state => { if (state.status === 'success') { console.log('User loaded:', state.data); } else if (state.status === 'error') { console.error('Error:', state.error); }});
userManager.execute(api.get<User>('/users/123').then(r => r.data));Conditional Promise Types
Заголовок раздела «Conditional Promise Types»// Promise или значение в зависимости от флагаtype MaybePromise<T, Async extends boolean = false> = Async extends true ? Promise<T> : T;
function getValue<T, A extends boolean = false>( value: T, async: A): MaybePromise<T, A> { if (async) { return Promise.resolve(value) as any; } return value as any;}
const syncValue = getValue('hello', false); // stringconst asyncValue = getValue('hello', true); // Promise<string>
// Awaitable typetype Awaitable<T> = T | Promise<T>;
async function process<T>(value: Awaitable<T>): Promise<T> { return await value; // Работает и с T, и с Promise<T>}
const result1 = await process(42); // numberconst result2 = await process(Promise.resolve(42)); // numberPromiseLike<T>
Заголовок раздела «PromiseLike<T>»// PromiseLike - для thenable объектовinterface PromiseLike<T> { then<TResult1 = T, TResult2 = never>( onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null ): PromiseLike<TResult1 | TResult2>;}
// Кастомный thenableclass CustomThenable<T> implements PromiseLike<T> { constructor(private executor: (resolve: (value: T) => void) => void) {}
then<TResult1 = T, TResult2 = never>( onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null ): PromiseLike<TResult1 | TResult2> { return new CustomThenable<TResult1 | TResult2>(resolve => { this.executor(value => { if (onfulfilled) { const result = onfulfilled(value); if (result instanceof CustomThenable) { result.then(resolve); } else { resolve(result as TResult1); } } }); }); }}
// Использование с async/awaitconst thenable = new CustomThenable<number>(resolve => { setTimeout(() => resolve(42), 100);});
const value = await thenable; // number: 42Type Guards для Promise
Заголовок раздела «Type Guards для Promise»// Type guard для Promisefunction isPromise<T>(value: T | Promise<T>): value is Promise<T> { return ( value !== null && typeof value === 'object' && 'then' in value && typeof value.then === 'function' );}
// Использованиеasync function handleValue<T>(value: T | Promise<T>): Promise<T> { if (isPromise(value)) { return await value; } return value;}
const result1 = await handleValue(42); // numberconst result2 = await handleValue(Promise.resolve(42)); // numberКлючевые моменты
Заголовок раздела «Ключевые моменты»Awaited<T>рекурсивно unwraps Promise типы- Критически важен для работы с async функциями и
ReturnType Promise<T>- встроенный generic тип для Promise объектовPromiseLike<T>- для thenable объектовAwaitedработает с вложенными Promise- Комбинируется с
ReturnTypeдля получения типа результата async функций - Используется для type-safe API clients и async state management
- Работает с
Promise.allдля правильного вывода типов массивов - Позволяет создавать conditional async/sync типы
- Доступен с TypeScript 4.5+