Я написал небольшой класс, который упаковывает указанную функцию, а также принимает список шаблонов проверки для каждого аргумента функции. Этот класс имеет функцию call
, которая должна принимать те же аргументы, что и функция ввода, проверять их, вызывать функцию ввода и переносить результат, например. в rxjs / наблюдаемый. Следовательно, конструктор должен проверить, совпадает ли число аргументов, указанное функцией method.length
, с количеством шаблонов проверки.
Необработанная версия JavaScript должна выглядеть примерно так:
class Wrapper {
method;
validationPatterns;
constructor(method, validationPatterns) {
if (method.length !== validationPatterns.length) {
// throw error
}
this.method = method;
this.validationPatterns = validationPatterns;
}
validate(...args) {
this.validationPatterns.forEach(pattern => {
// apply validation pattern
});
}
call(...args) {
// validate arguments
this.validate(...args);
// run method and wrap its result e.g. in an Observable
const methodResult = this.method(...args);
return Observable.of(methodResult);
}
}
Теперь я много гуглял и пробовал разные вещи, чтобы применять правильные типы к этому классу. Но я не мог найти подход, который позволяет мне
- доступ к числу параметров функции (через
method.length
) внутри конструктора. Поэтому невозможно создать экземпляр класса с ложной проверкой.
- и в то же время иметь правильную типизацию для всех функций класса.
Я пробовал следующие вещи:
1. Использование <T extends Function>
Плюсы:
- Я могу получить доступ к указанному методу внутри класса и в классе
экземпляры с правильными типами.
Минусы:
- Нет способа извлечь типы параметров и вернуть тип из
T
и примените их к validate()
и call()
. У них еще есть
использовать типы any
/ any[]
.
Пример кода:
class Wrapper<F extends Function> {
constructor(public method: F, public validationPatterns: any[]) {
if (method.length !== validationPatterns.length) {
// throw error
}
}
validate(...args: any[]): void {
// ...
}
call(...args: any[]): any {
// ...
}
}
2. Использование одного параметра функции.
Плюсы:
- Таким образом, можно применять правильные типы ко всем функциям.
Минусы:
- Функции, которым требуется более одного аргумента, должны обернуть их внутри правильно набранного кортежа или объекта. Это требует дополнительного синтаксиса (например, скобки
[a, b]
) при написании и вызове метода. Но это все равно должно быть приемлемым.
- Однако мы не можем проверить количество шаблонов проверки, потому что
method.length == 1
. Следовательно, ради полной типизации мы должны были бы признать недостатки функциональности, что мне не нравится.
Пример кода:
class Wrapper<T, U> {
constructor(public method: (arg: T) => U, public validationPatterns: any[]) {
// CANNOT check correct number of validation patterns
}
validate(args: T): void {
// ...
}
call(args: T): Observable<U> {
// ...
}
}
3. Перегрузки
Поскольку у обернутых функций обычно будет только несколько параметров, в другом подходе мы могли бы попытаться использовать перегрузку функций. Однако до сих пор я нашел информацию только о перегрузке одной функции. Я предполагаю, что я действительно хочу перегруженный класс, где сигнатуры call()
и validate()
зависят от перегрузки конструктора.
У меня нет опыта с перегрузкой в машинописи, но я думаю, пример кода с независимо перегруженными функциями может выглядеть примерно так:
class Wrapper<F extends Function, T1, T2, T3, U> {
constructor(method: (arg1: T1, arg2: T2, arg3: T3) => U, validationPatterns: any[]);
constructor(method: (arg1: T1, arg2: T2) => U, validationPatterns: any[]);
constructor(method: (arg1: T1) => U, validationPatterns: any[]);
constructor(method: () => U, validationPatterns: any[]);
constructor(public method: F, public validationPatterns: any[]) {
// ...
}
validate(arg1: T1, arg2: T2, arg3: T3): void;
validate(arg1: T1, arg2: T2): void;
validate(arg1: T1): void;
validate(): void;
validate(...args: any[]): void {
// ...
}
call(arg1: T1, arg2: T2, arg3: T3): Observable<U>;
call(arg1: T1, arg2: T2): Observable<U>;
call(arg1: T1): Observable<U>;
call(): Observable<U>;
call(...args: any[]): Observable<U> {
// ...
}
}
Вопросы
- Есть ли способ получить и то и другое: полный набор и доступ к номеру аргумента функции?
- Можно ли использовать перегрузку так, как я собираюсь?
- Какой подход вы мне порекомендуете? В настоящее время я иду с первым (
<T extends Function>
), который имеет полную функциональность, но отсутствует типы.
Спасибо!