Typescript ReturnType функции, возвращающей Promise

Я борюсь с чем-то, что, я уверен, должно быть простым в 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>'.

• Пожалуйста, отредактируйте , чтобы предоставить Whatever или достаточное количество, чтобы это был минимально воспроизводимый пример мы могли бы просто посмотреть в наших IDE вместо того, чтобы анализировать «как следует из названия» и т. д. и попытаться представить, что будет произошло бы, если бы это был настоящий код. • TS имеет синтаксическое требование отмечать возвращаемые типы async знаком Promise. Вместо Promise<ReturnType надо написать Promise<Awaited<ReturnType наверное... хотя без минимально воспроизводимого примера я просто предполагаю.

jcalz 02.06.2024 21:04

Я предположил, соответствует ли эта ссылка на игровую площадку вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 02.06.2024 21:05

@jcalz Интересно - я раньше не видел Awaited. Это действительно так, и так просто, как я и ожидал (хотя, честно говоря, не очевидно). Спасибо!

Zack Newsham 02.06.2024 22:02
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
3
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обратите внимание, что в целом не стоит возиться с универсальными условными типами , такими как 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> — он действительно намного чище!

Zack Newsham 02.06.2024 23:16

Другие вопросы по теме