Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Утилітний тип awaited у TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Awaited<T>** - утилітний тип TypeScript (з версії 4.5), який рекурсивно розгортає ланцюги Promise і повертає тип фінального значення. ```typescript type A = Awaited<Promise<string>>; // string type B = Awaited<Promise<Promise<number>>>; // number ``` **Ключове:** поєднуй з `ReturnType` для точної типізації async-функцій: `Awaited<ReturnType<typeof fn>>`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Awaited<T>** - утилітний тип TypeScript, доступний з версії 4.5, який рекурсивно розгортає ланцюги Promise і повертає тип фінального значення. ## Теорія ### TL;DR - Аналогія: кожен Promise - це обгортка. Awaited знімає всі обгортки, скільки б шарів не було. - Основна поведінка: розгортає одиночні Promise, вкладені Promise та union-типи. Якщо T не є Promise - повертає T без змін. - Правило вибору: `Awaited<ReturnType<typeof fn>>` - стандартний патерн для типізації async-функцій. - Доступний з TypeScript 4.5. ### Швидкий приклад ```typescript // Один шар type A = Awaited<Promise<string>>; // string // Вкладені - TypeScript рекурсує автоматично type B = Awaited<Promise<Promise<number>>>; // number // Union - розподіляється по кожному члену type C = Awaited<Promise<string> | number>; // string | number // Практичний приклад з async-функцією async function fetchUser(): Promise<{ id: number }> { return { id: 42 }; } type UserType = Awaited<ReturnType<typeof fetchUser>>; // { id: number } ``` TypeScript знімає шари Promise один за одним, доки не дійде до звичайного типу. Важливий момент з union: `Awaited<Promise<A> | B>` дає `A | B`, а не `Promise<A | B>`. ### Як працює рекурсивне розгортання TypeScript реалізує `Awaited` як умовний (conditional) тип: якщо `T extends Promise<infer U>` - рекурсує на `Awaited<U>`, інакше повертає `T`. Все відбувається на рівні компілятора під час перевірки типів. V8 і Node цього не бачать. Розподіл по union вбудований. `Awaited<Promise<A> | Promise<B>>` стає `A | B`, бо TypeScript застосовує умовний тип до кожного члена union окремо. Це корисно в API-клієнтах, де функція може повернути `Promise<ResponseA> | Promise<ResponseB>` залежно від параметрів. Особисто бачив, як це ловить розробників, що працюють зі старими SDK-обгортками, які повертають `Promise<Promise<T>>` через подвійне загортання. Awaited впорається з цим без жодних змін з твого боку. ### Коли використовувати - **Типізація повернення async-функцій:** `Awaited<ReturnType<typeof fn>>` дає чистий тип без ручних тверджень типу. - **API-ланцюги:** коли глибина вкладеності Promise залежить від SDK або версії бібліотеки. - **Union async-результатів:** `Awaited<Promise<string> | Promise<number>>` згортається до `string | number`. - **Типізація `Promise.all`:** у парі з mapped type по кортежу для розгортання кожного елемента. Не варто використовувати, якщо T вже є звичайним типом. `Awaited<string>` повертає `string` - технічно правильно, але користі нуль. ### Типові помилки **Застосування Awaited до функції замість її типу повернення:** ```typescript type Fn = () => Promise<number>; type Wrong = Awaited<Fn>; // never type Right = Awaited<ReturnType<Fn>>; // number ``` Тип функції не розширює `Promise<infer U>`, тому Awaited повертає `never`. Завжди використовуй `ReturnType` спочатку. **Самописний однорівневий unwrap, що ламається на вкладених Promise:** ```typescript // Старий патерн (до TypeScript 4.5) type OldWay<T> = T extends Promise<infer U> ? U : T; type A = OldWay<Promise<Promise<string>>>; // Promise<string> - ще обгорнуто type B = Awaited<Promise<Promise<string>>>; // string - правильно ``` Самописна версія не рекурсує. Якщо бачиш такий патерн у легасі-коді - заміни на `Awaited`. **Забутий ReturnType при роботі з async-функціями:** ```typescript async function loadData() { return { id: 1, name: "Alice" }; } type T1 = ReturnType<typeof loadData>; // Promise<{ id: number; name: string }> type T2 = Awaited<ReturnType<typeof loadData>>; // { id: number; name: string } ``` **Очікування, що Awaited розгортатиме поля об'єкта:** ```typescript type Obj = { data: Promise<string> }; type T = Awaited<Obj>; // Obj - без змін ``` Awaited розгортає тільки верхній рівень Promise. Вкладені Promise всередині об'єктів залишаються як є. Для цього потрібен рекурсивний mapped type. ### Реальне використання - **TanStack Query:** `type Data = Awaited<ReturnType<typeof fetchUser>>` для типізації даних хука без ручних тверджень типу. - **tRPC:** `inferRouterOutputs<AppRouter>` використовує Awaited під капотом для типізації результатів процедур. - **Express async-хендлери:** `Awaited<ReturnType<typeof handler>>` для виведення типу повернення middleware. - **Zod з async-трансформами:** коли схема містить async-перетворення, Awaited правильно розв'язує тип виходу. ### Питання на співбесіді **Q:** Що повертає `Awaited<Promise<void>>`? **A:** `void`. TypeScript не перетворює `void` на `never`. Це правильна поведінка для fire-and-forget функцій: awaiting їх дає `void`, і Awaited це відображає. **Q:** Як Awaited розподіляється по union-типах? **A:** `Awaited<Promise<A> | B>` стає `Awaited<Promise<A>> | Awaited<B>`, тобто `A | B`. TypeScript застосовує умовний тип до кожного члена union окремо. **Q:** Чим Awaited відрізняється від старого ручного патерну розгортання? **A:** Ручний патерн `T extends Promise<infer U> ? U : T` знімає рівно один шар. Awaited рекурсує до не-Promise типу і підтримує union-розподіл. **Q:** Реалізуй Awaited вручну без вбудованого типу. **A:** ```typescript type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T; ``` Це покриває рекурсію. Розподіл по union TypeScript виконує автоматично для дистрибутивних умовних типів. **Q:** Чому в React Query краще `Awaited<ReturnType<typeof fn>>`, ніж ручна анотація `data`? **A:** Тому що тип відстежує сигнатуру `queryFn` автоматично. Зміниш тип повернення функції - тип `data` оновиться разом із нею. Ручна анотація може застаріти без жодного попередження компілятора. ## Приклади ### Базовий: отримання типу з async-функції ```typescript interface User { id: number; name: string; } async function fetchUser(id: number): Promise<User> { const res = await fetch(`/api/users/${id}`); return res.json(); } // Тільки ReturnType - Promise залишається type Wrapped = ReturnType<typeof fetchUser>; // Promise<User> // Додаємо Awaited - шар Promise знятий type Resolved = Awaited<ReturnType<typeof fetchUser>>; // User ``` `ReturnType` витягує те, що функція повертає, включно з Promise. `Awaited` знімає обгортку. Разом це стандартний патерн для типізації async-результатів у продакшен-коді. ### Типізація хука React Query ```typescript interface Post { id: number; title: string; } async function fetchPost(id: number): Promise<Post> { const res = await fetch(`/api/posts/${id}`); return res.json(); } type PostData = Awaited<ReturnType<typeof fetchPost>>; // Post function usePost(id: number) { return useQuery({ queryKey: ["post", id], queryFn: () => fetchPost(id), // data автоматично виводиться як Post | undefined }); } ``` TanStack Query v5 виводить тип `data` з `queryFn` через Awaited під капотом. Ти отримуєш `Post | undefined` без жодної ручної анотації типу на хуку. ### Граничний випадок: union з void ```typescript type Complex = Promise<Promise<string> | void>; type Resolved = Awaited<Complex>; // Крок 1: Awaited<Promise<string> | void> // Крок 2: Awaited<Promise<string>> | Awaited<void> // Крок 3: string | void ``` `Awaited<void>` залишається `void`, а не `never`. Якщо треба прибрати `void` з union-результату - використовуй `Exclude<Resolved, void>`, що дасть `string`. Awaited і Exclude - дві окремі операції.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.