Типизация хелпера, принимающего либо значение, либо Promise от него
Геттер кэша может уже держать значение, а может ещё грузить его. Реализуйте resolve так, чтобы он принимал обе формы и всегда отдавал вызывающему готовое значение того же типа.
Ограничения: один параметр типа; вызывающий, написав await resolve(x), обязан получить T, а не T | Promise<T>; никаких as и никакого any. Считайте, что input никогда не бывает thenable, который не является настоящим Promise.
type MaybePromise<T> = T | Promise<T>;
async function resolve<T>(input: MaybePromise<T>): Promise<T> {
// ваш код здесь
}
Допишите реализацию.
Принимайте T | Promise<T>, а отдавайте Promise<T>. Внутри async-функции await input приводит обе формы к T, и даже голый return input проходит проверку, ведь позиция возврата у async и так принимает значение или thenable. Чего делать нельзя — выпускать T | Promise<T> наружу: объединение в возврате заставит каждого вызывающего ветвиться, а Promise<T> позволит просто сделать await.
- ✗Выпускать
T | Promise<T>наружу как тип возврата, перекладывая ветвление на вызывающих - ✗Считать, что
awaitнад не-промисом — ошибка, а не пустая операция - ✗Ждать, что
async-функция дважды обернёт возвращаемый ею промис
- →Как
Awaited<T>меняет эту сигнатуру, еслиTсам может бытьPromise? - →Почему
return inputпроходит проверку, хотяinputможет и не бытьPromise?
Решение
type MaybePromise<T> = T | Promise<T>;
async function resolve<T>(input: MaybePromise<T>): Promise<T> {
return await input;
}
Одна строка — и весь смысл в том, что await над не-промисом не ошибка: он просто отдаёт значение как есть (формально прогоняет его через микрозадачу). Тип await над T | Promise<T> — это Awaited<T | Promise<T>>, что для не-промисного T сводится к T.
Голый return input тоже проходит проверку, потому что позиция возврата async-функции с типом Promise<T> принимает T | PromiseLike<T>:
async function resolve<T>(input: MaybePromise<T>): Promise<T> {
return input; // тоже валидно
}
Чего делать нельзя
// ❌ объединение утекает наружу — ветвиться придётся каждому вызывающему
function resolveBad<T>(input: MaybePromise<T>): MaybePromise<T> {
return input;
}
const v = resolveBad(maybe);
if (v instanceof Promise) { /* ... */ } else { /* ... */ } // ❌ на каждом вызове
MaybePromise<T> уместен на входе и вреден на выходе: асинхронность заразна, и хелпер должен поглотить неопределённость, а не размножить её.
⚠️ Если T сам может оказаться промисом, точный тип возврата — Promise<Awaited<T>>: await разворачивает рекурсивно, поэтому Promise<T> в этом случае был бы ложью.