Я борюсь с чем-то, что, я уверен, должно быть простым в Typescript. Я пытаюсь определить вспомогательную функцию, которая запускает некоторую функцию и регистрирует пользовательское сообщение в случае ошибки (это упрощение реальной функциональности, но для примера этого достаточно).
async function logIt<T extends () => Promise<any>>(message: string, fn: T): ReturnType<T> {
try {
const result = await fn();
return result;
}
catch (e) {
console.info(message);
throw e;
}
}
Затем я использую его:
class Thing extends Whatever {
// as the name suggests, this function is overwritten from `Whatever` - it's returnType is `Promise<number>`
functionOnWhatever() {
return logIt("hello", () => super.functionOnWhatever());
}
}
Проблема в том, что функция logIt жалуется, что ReturnType<T> должно быть Promise<ReturnType<T>>, и когда я вношу это изменение, functionOnWhatever жалуется, что 'Promise<Promise<number>>' is not assignable to 'Promise<number>'.
Я предположил, соответствует ли эта ссылка на игровую площадку вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?
@jcalz Интересно - я раньше не видел Awaited. Это действительно так, и так просто, как я и ожидал (хотя, честно говоря, не очевидно). Спасибо!






Обратите внимание, что в целом не стоит возиться с универсальными условными типами , такими как ReturnType<T> для универсальных T. TypeScript не может правильно анализировать такие типы. Рекомендуемый подход к такой функции, как logIt(), — написать ее так:
async function logIt<T>(message: string, fn: () => Promise<T>): Promise<T> {⋯}
который обходит всю вашу проблему. Но если вам нужно использовать ReturnType, продолжайте читать:
Проблема, с которой вы столкнулись, заключается в том, что ReturnType<T> известно только как Promise<any>, но у TypeScript есть строгое требование, чтобы асинхронная функция могла возвращать только Promise, а не какой-то возможный подтип Promise. См. microsoft/TypeScript#35191.
Это потому, что функции async всегда оборачивают результаты в новый Promise; даже если функция возвращает что-то, что можно присвоить Promise, оно не будет просто передано без изменений.
Если бы logit() было разрешено возвращать ReturnType<T>, где T — тип fn, то могло бы произойти что-то вроде следующего:
async function logIt<T extends () => Promise<any>>(
message: string, fn: T): ReturnType<T> {⋯}
const myPromise = Object.assign(Promise.resolve(1), { prop: "abc" });
// ^? const myPromise: Promise<number> & { prop: string; }
console.info(myPromise.prop.toUpperCase()); // ABC
const newPromise = logIt("myPromise", () => myPromise);
// ^? const newPromise: Promise<number> & { prop: string; }
console.info(newPromise.prop.toUpperCase()); // 💥 RUNTIME ERROR!
Тип myPromise — это Promise<number> с дополнительным свойством, а значит, и newPromise тоже. Но на самом деле это не так. Это дополнительное свойство исчезает во время выполнения.
Так что вам обязательно нужно вернуть Promise<⋯> вместо ReturnType<T>. Но Promise<ReturnType<T>> неверно, поскольку ReturnType<T> уже можно присвоить Promise.
Что вам нужно сказать, так это то, что это Promise<X> где X тот тип, который вы получаете, когда ждете ReturnType<T>. Для этого вы можете использовать тип утилиты Awaited<T>:
async function logIt<T extends () => Promise<any>>(
message: string, fn: T
): Promise<Awaited<ReturnType<T>>> {
try {
const result = await fn();
return result;
}
catch (e) {
console.info(message);
throw e;
}
}
Это работает. Awaited<ReturnType<T>> развернёт любую Promise обертку для T, а затем вы завернёте её в Promise самостоятельно с помощью Promise<Awaited<ReturnType<T>>>. Если ReturnType<T> — это Promise<number>, то Awaited<ReturnType<T>> — это number, а Promise<Awaited<ReturnType<T>>> — это снова Promise<number>. Если, с другой стороны, ReturnType<T> — это Promise<number> & {prop: string}, то Awaited<ReturnType<T>> — это number, а Promise<Awaited<ReturnType<T>>> — это снова Promise<number>. {prop: string} исчезает, как и то, что происходит во время выполнения.
Детская площадка, ссылка на код
Спасибо, что указали и на фрагмент Promise<T> — он действительно намного чище!
• Пожалуйста, отредактируйте , чтобы предоставить
Whateverили достаточное количество, чтобы это был минимально воспроизводимый пример мы могли бы просто посмотреть в наших IDE вместо того, чтобы анализировать «как следует из названия» и т. д. и попытаться представить, что будет произошло бы, если бы это был настоящий код. • TS имеет синтаксическое требование отмечать возвращаемые типыasyncзнакомPromise. ВместоPromise<ReturnTypeнадо написатьPromise<Awaited<ReturnTypeнаверное... хотя без минимально воспроизводимого примера я просто предполагаю.