Как набрать функцию, которая переносит другую функцию по имени в TypeScript - PullRequest
0 голосов
/ 26 августа 2018

Вот что у меня есть:

function wrapCallByName<T extends any[], R>(functionName: keyof some.api.Api) {
  return (...args: T) => {
    try {
      some.api()[functionName](...args);
    } catch (error) {
      myHandleApiError(error);
    }
  }
}

В нем говорится, что some.api()[functionName] может быть undefined и что он не знает, какими типами будут его аргументы.

Но это не могло быть (то есть, вероятно, не будет) undefined из-за типа functionName, и мы знаем, какие будут типы.

В идеале тип возвращаемого значения wrapCallByName - это сигнатура функции some.api () [functionName].

Есть ли способ ввести это правильно в TypeScript?

1 Ответ

0 голосов
/ 26 августа 2018

Эта проблема состоит из двух частей, первая - правильная подпись функции.Мы хотим, чтобы функция взяла ключ к some.Api и вернула значение того же типа, что и исходный ключ.Для этого нам понадобится дополнительный параметр типа K, который расширяет keyof some.Api, и мы будем использовать запрос типа, чтобы сообщить компилятору, что возвращаемый результат совпадает с типом переданного поля (some.Api[K])

declare namespace some {
    type Api = {
        foo(n: number): string;
        bar(s: string, n: number) : string
    }
    function api(): Api;
}


function wrapCallByName<K extends keyof some.Api>(functionName: K) : some.Api[K] {
    return ((...args: any[]) => {
        try {
            return (some.api()[functionName] as any)(...args);
        } catch (error) {
            throw error;
        }
    }) as any;
}

const foo = wrapCallByName('foo')
foo(1) //returns string

const bar = wrapCallByName('bar')
bar('', 1) //returns string

Playground

Как вы видите, в приведенной выше реализации есть много утверждений типа для любого.Это потому, что есть несколько проблем.Во-первых, индексный доступ к API приведет к не вызываемому объединению всех полей в API.во-вторых, функция, которую мы возвращаем, не совместима ни с одним полем API.Чтобы обойти это, мы можем использовать дополнительное косвенное обращение, которое заставит компилятор рассматривать значения объекта как функции с сигнатурой (... a: any[]) =>any.Это исключит необходимость каких-либо утверждений.

declare namespace some {
    type Api = {
        foo(n: number): string;
        bar(s: string, n: number): string
    }
    function api(): Api;
}

function wrapBuilder<T extends Record<keyof T, (...a: any[]) => any>>(fn: () => T) {
    return function <K extends keyof T>(functionName: K): T[K] {
        return ((...args: any[]) => {
            try {
                return fn()[functionName](...args);
            } catch (error) {
                throw error;
            }
        });
    }
}
const wrapCallByName = wrapBuilder(() => some.api());
const foo = wrapCallByName('foo');
foo(1); //returns string

const bar = wrapCallByName('bar');
bar('', 1); //returns string

Детская площадка

Я упомянул эту замечательную реализацию без утверждений, потому что ваш вопрос специально его опровергает, лично мне будет достаточно комфортно с первой версией, особенно еслиВаш API выставляет только функции.Функция-обертка должна быть написана только один раз, и я не могу вспомнить ситуацию, когда утверждения вызовут проблему, тем более важной частью является то, что публичная подпись правильно перенаправляет типы.

...