Я пытаюсь создать тип, который принимает тип функции, оборачивает параметры функции в Promise<>
и возвращает новый тип — ту же функцию, но с параметрами, как у Promise, например:
type PromisedFn = PromisedArgs<(a: number, b: string | number) => void>
// should produce (a: Promise<number>, b: Promise<string | number>) => void
После нескольких часов ломания головы мне удалось получить только такой код:
type Func = (id: number, guid: number | string) => number | string;
type FuncParams = Parameters<Func>;
// FuncParams = [id: string, guid: string | number]
type FuncWrappedParams = {
[K in keyof FuncParams as Extract<K, '0'|'1'>]: Promise<FuncParams[K]>
}
// { 0: Promise<number>, 1: Promise<string | number> }
Тем не менее объект с числовым индексом не может быть правильно применен как массив:
type FuncWrappedParamsArray = [...args: FuncWrappedParams[]];
type WrappedParamsFunc = (...args: FuncWrappedParamsArray) => ReturnType<Func>;
let func: WrappedParamsFunc = (
id: Promise<number>,
guid: Promise<number | string>
) => 'TestString';
// ERROR:
// Type '(id: Promise<number>, guid: Promise<number | string>) => string'
// is not assignable to type 'WrappedParamsFunc'.
// Types of parameters 'id' and 'args' are incompatible.
// Type 'FuncWrappedParams' is missing the following properties from type 'Promise<number>':
// then, catch, [Symbol.toStringTag]
Я понятия не имею, как справиться с этим.
Проблема №1 описана выше.
Проблема №2: недостаток в том, что мы должны заранее знать ряд параметров функции: [K in keyof FuncParams as Extract<K, '0'|'1'>]
.
Вы можете написать PromisedArgs
так:
type PromisedArgs<T extends (...args: any) => any> =
T extends (...args: infer A) => infer R ? (
(...args: { [I in keyof A]: Promise<A[I]> }) => R
) : never;
Пока ваш тип массива является параметром типа универсальный (например, A
выше), сопоставленный тип поверх него создаст другой тип массива. В {[I in keyof A]: ...}
тип ключа I
, по сути, автоматически перебирает числовые индексы. Вам не нужно вручную захватывать "0" | "1" | "2" | ...
или беспокоиться о том, как продвигать полученный сопоставленный тип обратно в массив.
Использование ключевое слово infer
в проверке условного типа — это просто способ одновременно получить параметры и возвращаемый тип функции вместо использования Parameters<T>
и ReturnType
типы утилит по отдельности. Если вы посмотрите на определения из Parameters
и из ReturnType
, вы увидите, что они реализованы одинаково.
В любом случае, давайте удостоверимся, что это работает так, как задумано:
type Func = (id: number, guid: number | string) => number | string;
type WrappedParamsFunc = PromisedArgs<Func>
/* type WrappedParamsFunc =
(id: Promise<number>, guid: Promise<string | number>) => string | number */
let func: WrappedParamsFunc = (
id: Promise<number>,
guid: Promise<number | string>
) => 'TestString';
Выглядит неплохо!
Кстати, в TypeScript есть ошибка, о которой сообщалось на Майкрософт/TypeScript#27995, которая могла помешать вам найти это решение самостоятельно. Оказывается, важно, чтобы вы отображали ключи массива, подобного параметр универсального типа. Если вы попытаетесь сопоставить определенный тип, сопоставление вернется к повторению всех ключей, даже методов массива и прочего:
type Foo = [1, 2, 3]
type Bar = { [I in keyof Foo]: Promise<Foo[I]> };
/* type Bar = {
[x: number]: Promise<3 | 1 | 2>;
0: Promise<1>;
1: Promise<2>;
2: Promise<3>;
length: Promise<3>;
toString: Promise<() => string>;
...
*/
Поскольку Foo
— это конкретный тип, а не параметр типа, вы получаете путаницу выше. Ваш FuncParams
относится к определенному типу, как и Foo
, и вы пытались навести порядок вручную, что не совсем сработало. Ну что ж!
Ссылка на код для игровой площадки
@T.J.Crowder, ну, есть stackoverflow.com/questions/66429203/…, но я не знаю, соответствует ли это вашему критерию.
Итак, из-за ошибки, когда Любые я хочу создать тип массива из другого типа массива, мне нужен этот условный трюк, верно? (Это действительно то место, где я застрял - и, как вы говорите, я тоже думаю, что OP - когда я пытался решить это вчера.) Знаете ли вы, есть ли у нас чистый "Как мне создать новый тип массива из тип массива?" вопрос с этим в качестве ответа? Я вчера не смог найти.