简体   繁体   中英

Typescript Universal method decorator returning Promise as value

I'm trying to implement a universal method decorator that can be used with both prototype and instance methods.

Based off: Typescript decorators not working with arrow functions

As you can see from the code below, instanceMethod() is returning a Promise.

Is there a way I can return the correct value?

Decorator code:

export function trace(verbose: boolean = false) {

    return (target: any, prop: string, descriptor?: TypedPropertyDescriptor<any>): any => {
        let fn
        let patchedFn

        if (descriptor) {
            fn = descriptor.value
        }

        return {
            configurable: true,
            enumerable: false,
            get() {
                if (!patchedFn) {
                    patchedFn = async (...args) => {
                        const ret = fn.call(this, ...args)
                        console.log(`typeof ret: ${typeof ret}`)
                        console.log(`return value: ${ret}`)
                        return ret
                    }
                }
                console.log(`get() patchedFn: ${patchedFn}`)
                return patchedFn
            },
            set(newFn) {
                console.log(`set() newFn: ${newFn}`)
                patchedFn = undefined
                fn = newFn
            },
        }

    }
}

Test class & test code:

class Greeter {

    @trace()
    public instanceMethod(s: string): string {
        return s
    }

    @trace()
    public async instanceAsyncMethod(s: string): Promise<string> {
        return s
    }

    @trace()
    public instanceArrowMethod = (s: string): string => {
        return s
    }

}

async function test() {
    const greeter = new Greeter()
    const result1 = greeter.instanceMethod('1')
    console.log(`* instanceMethod: ${result1}`) // should return '1', instead returns a Promise
    const result2 = await greeter.instanceAsyncMethod('2')
    console.log(`* instanceAsyncMethod: ${result2}`)
    const result3 = await greeter.instanceArrowMethod('3')
    console.log(`* instanceArrowMethod: ${result3}`)

}

test()

Output:

set() newFn: (s) => {
            return s;
        }
get() patchedFn: (...args) => __awaiter(this, void 0, void 0, function* () {
                        console.log(`typeof fn: ${typeof fn}`);
                        const ret = fn.call(this, ...args);
                        console.log(`typeof ret: ${typeof ret}`);
                        console.log(`return value: ${ret}`);
                        return ret;
                    })
typeof ret: string
return value: 1
* instanceMethod: [object Promise] <<<<<<<<<< The instance method is returning a Promise
get() patchedFn: (...args) => __awaiter(this, void 0, void 0, function* () {
                        console.log(`typeof fn: ${typeof fn}`);
                        const ret = fn.call(this, ...args);
                        console.log(`typeof ret: ${typeof ret}`);
                        console.log(`return value: ${ret}`);
                        return ret;
                    })
typeof ret: object
return value: [object Promise]
* instanceAsyncMethod: 2
get() patchedFn: (...args) => __awaiter(this, void 0, void 0, function* () {
                        console.log(`typeof fn: ${typeof fn}`);
                        const ret = fn.call(this, ...args);
                        console.log(`typeof ret: ${typeof ret}`);
                        console.log(`return value: ${ret}`);
                        return ret;
                    })
typeof ret: string
return value: 3
* instanceArrowMethod: 3

An async function will always return a promise.

You have patchedFn defined as an async function, you will either have to modify to declare it as normal function, or await at the caller if it is intended.

Based on Edward's answer above, I fixed the issue with the following code.

I've tested it on arrow function, instance and static methods.

export function trace(verbose: boolean = false) {

    return (target: any, prop: string, descriptor?: TypedPropertyDescriptor<any>): any => {
        let fn
        let patchedFn

        if (target instanceof Function) {
            // for static methods return function itself
            return target
        }

        if (descriptor) {
            fn = descriptor.value
        }

        return {
            configurable: true,
            enumerable: false,
            get() {
                if (!patchedFn) {
                    patchedFn = (...args) => {
                        const ret = fn.apply(this, args)
                        if (ret instanceof Promise) {
                            // execute the promise
                            ret.then((data) => {
                                return data
                            }).catch((error) => {
                                console.log(error)
                            })
                        }
                        return ret
                    }
                }
                return patchedFn
            },
            set(newFn) {
                patchedFn = undefined
                fn = newFn
            },
        }

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM