Typescript, decorate async function

Viewed 10000

I'm trying to decorate async function#1 with some async function#2.

E.g.

function func2(param) {
   return (target: any, propertyKey: string, descriptor: PropertyDescriptor) =>    {
   //make async operations and then return descriptor
}


@func2(param)
async function func1() {
    await .... //some async operation
    await .... //some async operation
}

So, the main idea is to perform some async operation in decorator, and then perform other async calls in the main function.

Is it possible to make this withing typescript decorators?

Thank you in advance.

2 Answers

Decorators can only be used on a class method not on a regular function, so that is one limitation, but if you put the function within a class you can easily replace the original function and perform other async tasks:

function func2(param: number) {
    return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<(... params: any[])=> Promise<any>>) => {
        let oldFunc = descriptor.value;
        descriptor.value = async function (){
            var result = await oldFunc.apply(this, arguments);
            await delay(param) //some async operation
            console.log("delay 3");
            return result;
        }
    }
}

class Test {
    @func2(1000)
    async func1(timout: number) {
        await delay(timout) //some async operation
        console.log("delay 1");
        await delay(timout) //some async operation
        console.log("delay 2");
    }
}

new Test().func1(1000);
// Util function 
async function delay(timeout: number) {
    return new Promise<void>((resolve) => setTimeout(() => {
        resolve();
    }, timeout));
}

In case someone stumbles upon the same problem I had and wants to retrieve some value with an async function decorator (in my case access token object from DB) and pass it on to a method you can use something along these lines.

You simply need to push to the arguments array.

Decorator function

export const authorizeXero = () => {
    return (target: any, propertyKey: string, descriptor: any) => { 
        const fn = descriptor.value;
        descriptor.value = async function (...args: any[]) { // anything that was passed in the arguments in instance method (it can be empty)
            let tokens = await fetchSavedXeroTokens()
            tokens = await checkIfXeroTokensStillValid({...tokens})
            tokens = await refreshXeroTokens({...tokens})
            tokens = await saveXeroRefreshToken({...tokens})
            const {access_token,refresh_token} = tokens
            args.push(access_token) // pushes value to the array that will be available to decorated methods
            return fn.apply(this, args);
        }    
    }
} 

Example of an Instance method

@authorizeXero()
async getLatestInvoices (...params:any) {
    const accessToken = params[0] // everything that is stored in args under decorator function
    const allInvoices = await this.fetchInvoices({accessToken,maxPages:30})
    const xe = allInvoices.map((invoice:any)=>{
        return {'invName':invoice.InvoiceNumber,'orderName':invoice.Reference}
    })
    return xe
}
Related