Можно ли обернуть функцию и сохранить ее типы?

Я пытаюсь создать общую функцию обертки, которая будет обертывать любую переданную ему функцию.

В самом начале функция обертки будет выглядеть примерно так:

function wrap<T extends Function>(fn: T) {
    return (...args) => {
        return fn(...args)
    };
}

Я пытаюсь использовать его как:

function foo(a: string, b: number): [string, number] {
    return [a, b];
}

const wrappedFoo = wrap(foo);

Прямо сейчас wrappedFoo получает тип (...args: any[]) => any

Можно ли wrappedFoo чтобы имитировать типы этой функции?

Ответ 1

Можно создать функцию-оболочку, которая принимает и возвращает те же типы, что и функция, ее обертывание, делая 2 изменения

  1. укажите возвращаемое значение функции-обертки как T тип, который вы обертываете
  2. введите функцию, которую вы возвращаете, в <any>

Например:

function wrap<T extends Function>(fn: T): T {
    return <any>function(...args) {
        return fn(...args)
    };
}

Затем тип const wrappedFoo = wrap(foo);

будет правильно:

(a: string, b: number) => [string, number].

Ответ 2

Это работает с любым количеством аргументов и сохраняет все аргументы & типы возврата

const wrap = <T extends Array<any>, U>(fn: (...args: T) => U) => {
  return (...args: T): U => fn(...args)
}

Ответ 3

Вы можете использовать перегрузки для предоставления определенных типов для обертывания функций с параметрами 0, 1, 2, 3, 4 или более. Если одна из ваших функций принимает еще больше параметров, добавьте дополнительную перегрузку или просто отбросьте ее на аргумент остальных аргументов.

function wrap<TResult>(fn: () => TResult) : () => TResult;
function wrap<T1, TResult>(fn: (param1 : T1) => TResult) : (param1 : T1) => TResult;
function wrap<T1, T2, TResult>(fn: (param1 : T1, param2 : T2) => TResult) : (param1 : T1, param2 : T2) => TResult;
function wrap<T1, T2, T3, TResult>(fn: (param1 : T1, param2 : T2, param3 : T3) => TResult) : (param1 : T1, param2 : T2, param3 : T3) => TResult;
function wrap<T1, T2, T3, T4, TResult>(fn: (param1 : T1, param2 : T2, param3 : T3, param4 : T4) => TResult) : (param1 : T1, param2 : T2, param3 : T3, param4 : T4) => TResult;
function wrap<TParam, TResult>(fn: (...params : TParam[]) => TResult) : (...params : TParam[]) => TResult {
    return (...params) => {
        return fn(...params);
    };
}

Это не очень красиво, но это дает самый точный тип.

Ответ 4

Это возможно, но может стать немного беспорядочным, если вы хотите иметь возможность передавать разные типы и количество аргументов.

Ваш пример можно сделать следующим образом:

function wrap<A, B, C>(fn: (a: A, b: B) => C) {
    return (a: A, b: B): C => {
        return fn(a, b);
    };
}

Тогда тип:

const wrappedFoo = wrap(foo);

Is (a: string, b: number) => [string, number].
((fn: (a: A, b: B) => C) { return (a: A, b: B): C => { return fn(a, b); }; } function foo(a: string, b: number): [string, number] { return [a, b]; } const wrappedFoo = wrap(foo); rel=nofollow>код на детской площадке)

Но, как вы можете видеть, работать с ними не очень удобно, если у вас есть разные подписи, которые вы хотите использовать (например, мой пример работает только для двух параметров).

Что вы можете сделать, это передать только один параметр, который поддерживается интерфейсом:

function wrap<In, Out>(fn: (params: In) => Out) {
    return (params: In): Out => {
        return fn(params);
    };
}

interface FooParams {
    a: string;
    b: number;
}

function foo(params: FooParams): [string, number] {
    return [params.a, params.b];
}

const wrappedFoo = wrap(foo);

((fn: (params: In) => Out) { return (params: In): Out => { return fn(params); }; } interface FooParams { a: string; b: number; } function foo(params: FooParams): [string, number] { return [params.a, params.b]; } const wrappedFoo = wrap(foo); rel=nofollow>код на детской площадке)

По моему мнению, с этим будет гораздо легче работать.