Raz Ronen
Raz Ronen

Reputation: 2628

Typescript: How to pass property through this in decorator?

I have a retry decorator that I put over all my http calls for dependency.

Something like:

@retry(3)
callToGoogle(..)
@retry(2)
callToMicrosoft(..)

I need a way to detect which retry attempt is it inside the functions. I don't have the option to add the attempt as a parameter to the function because those functions have optional parameters and different locations in my code call them with different number of parameters.

For some reason when I deploy this source of code to production - the value retryAttempt in function foo is undefined. Weirdly, this works in unit test - meaning the retryAttempt is populated. I have no idea what this isn't working.

The local test is a node.js process holding my application and the prod environment is kubernetes that runs this process over multiple pods. I don't see a reason why that would matter. Please help me!

Code:

function retrySyncMethod(
    times: number,
    target: any,
    originalMethod: any,
    args: any[]): any {
    for (var attempt = 1; attempt < times; attempt++) {
        const nextAttempt: number = attempt + 1;
        target.currAttempt = nextAttempt;
        var result = originalMethod.apply(target, args);
        return result;
    }
}

export function retry(attempts: number):
    (target: Object,
        propertyKey: string,
        descriptor: TypedPropertyDescriptor<any>) => void {
    return function (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> {
        var originalMethod = descriptor.value; 

        descriptor.value = function (...args: any[]): any {
            var that = this;

            try {
                return originalMethod.apply(that, args);   
            } catch (err) {
                return retrySyncMethod(attempts, that, originalMethod, args);
            }
        };
        return descriptor;
    };
}

@retry(3)
function foo(host: string, path: string, optionaParam?: null) {
    const retryAttempt: number = (<any>this).currAttempt;
    if (retryAttempt > 2) {
        host = "changing host"
    }
    console.log("Mock Executing http request and throwing an error to simulate the need of retry");
    throw Error("Got 500 - throwing error in order to retry");
}

Upvotes: 1

Views: 1124

Answers (1)

Vahid
Vahid

Reputation: 1937

How about if you move the change host logic outside of your actual method and let the decorator call it? Like below:

function changeHostIfNecessary(args: any[], attempt: Number, host: string): any[] {
  if ( attempt > 3 ) {
      const [, ...rest] = args
      return [host, rest]
  }
  return args
}

function retry(attempts: number, fallbackHost: string){
  return function (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> {
      var originalMethod = descriptor.value; 
      descriptor.value = function (...args: any[]): any {
          var that = this;
          let currentAttempt = 1
          do {
              try {
                  const modArgs = changeHostIfNecessary(args, currentAttempt, fallbackHost)
                  originalMethod.apply(that, modArgs);   
              } catch (err) {
                  currentAttempt++
              }
              
          } while(currentAttempt <= attempts)    
          
      };
      return descriptor;
  };
}


class Google{
    @retry(5, "alternateHost")
    public call(host: string, path: string, optionaParam?: null){
      console.log(`Calling with host :${host}...`)
      throw Error("Simulating a runtime exception.....")
    }
}

const g = new Google()
g.call('InitialHost','doc/xyz')

Upvotes: 1

Related Questions