Использование метода-декоратора для изменения типа возвращаемого значения функции - PullRequest
0 голосов
/ 04 ноября 2019

Ниже у меня есть функция синхронизации, которая возвращает логическое значение, я использую этот декоратор @MakeAsync, чтобы изменить функцию. Мне интересно, как я могу обновить тип возвращаемого значения функции после того, как это изменение было сделано. Я попытался использовать Promise<ReturnType<typeof original>>, но это не удалось.

Возможно ли для декоратора переопределить определение типа (параметры и тип возвращаемого значения) метода?

function MakeAsync() {
    console.log("f(): evaluated");
    return function (target, key: string, descriptor: PropertyDescriptor) {
        const original = descriptor.value;
        if (typeof original === 'function') {
            descriptor.value = async (...args): Promise<ReturnType<typeof original>> => {
                return original(...args)
            }
        }
        return descriptor;
    }
}

class Example {    
    @MakeAsync()
    doTrue (): Boolean {
        return true
    }
}

const e = new Example

console.log(e.doTrue())

1 Ответ

0 голосов
/ 04 ноября 2019

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

Вот одна из возможностей:

type Asyncify<C extends new (...args: any) => any,
    K extends keyof InstanceType<C>> = new (...args: ConstructorParameters<C>) => {
        [P in keyof InstanceType<C>]: P extends K ?
        InstanceType<C>[K] extends (...args: infer A) => infer R ?
        (...args: A) => Promise<R> : InstanceType<C>[K]
        : InstanceType<C>[K]
    };

function makeMethodAsync<
    C extends new (...args: any) => any,
    K extends keyof InstanceType<C>
>(ctor: C, methodName: K): Asyncify<C, K> {
    const c = class extends (ctor as any) { };
    c.prototype[methodName as any] = async function (...args: any) {
        return ctor.prototype[methodName].apply(this, args);
    }
    return c as any;
}

Эта реализация используетмного any утверждений типа, чтобы успокоить компилятор. В основном тип Asyncify<C, K> принимает тип конструктора C и имя свойства экземпляра K (которое действительно должно быть именем метода; я не проверяю это) и вычисляется в другом типе конструктора, экземпляр которого возвращает Promise для этого метода K и оставляет другие свойства в покое. И реализация делает то же самое с прототипом нового подкласса, который создает. (Я попытался позаботиться о том, чтобы правильно разобраться с контекстом this; я думаю, что ваш декоратор может делать плохие вещи с экземплярами классов, чьи методы обращаются к свойствам или другим методам).

Давайте посмотрим, работает ли он:

const Example = makeMethodAsync(class Example {
    prop = "hey";
    syncDoTrue() {
        console.log(this.prop)
        return true;
    }
    asyncDoTrue() {        
        console.log(this.prop)
        return true;
    }

}, "asyncDoTrue");

const e = new Example(); 

const b = e.syncDoTrue(); // hey
console.log(b); // true

const r = e.asyncDoTrue(); // hey
console.log(r); // Promise { <state>: "fulfilled", <value>: true }

Мне кажется, выглядит хорошо. Надеюсь, это поможет;удачи!

Ссылка на код

...