如何在保持输入的同时使用 class 方法执行重试功能

How to do retry function with class methods, while keeping typing

我有一个 class,它基本上是访问 spotify 数据,它需要一些重试机制,我制作了一个 callWithRetry 方法,它使用传递的参数调用一个方法并进行重试class.

中的每个方法

export default SomeClass {
  ...
  private async callWithRetry < T > (functionCall: (args: any) => Promise < T > , args: any, retries = 0): Promise < T > {
    try {
      return await functionCall(args);
    } catch (error) {
      if (retries <= this.MAX_RETRIES) {
        if (error && error.statusCode === 429) {
          const retryAfter = (parseInt(error.headers['retry-after'] as string, 10) + 1) * 1000;
          console.log(`sleeping for: ${retryAfter.toString()}`);
          await new Promise((r) => setTimeout(r, retryAfter));
        }
        return await this.callWithRetry < T > (functionCall, args, retries + 1);
      } else {
        throw e;
      }
    }
  };
  public async getData(api: Api, id: string, offset: number = 1): Promise < string[] > {
    const response = await api.getStuff(id, {
      limit: 50,
      offset: offset
    });

    if (response.statusCode !== 200) {
      return []
    }

    const results = response.body.items.map(item => item.id);

    if (response.body.next) {
      const nextPage = await this.getData(api, id, offset + 1);
      return results.concat(nextPage);
    }
    return results
  }


  public async getOtherData(api: Api, offset: number = 0): Promise < string[] > {
    let response = await api.getOtherData({
      limit: 50,
      offset: offset
    });

    if (response.statusCode !== 200) {
      return []
    }

    const results = response.body.items.map(item => item.id);

    if (response.body.next) {
      const nextPage = await this.getOtherData(api, offset + 1);
      return results.concat(nextPage);
    }
    return results
  }
}

我的问题是我不能使用不同长度的参数列表函数,我必须将函数的参数列表放在一个对象中并将其键入为任何类型,这使我失去了类型安全性。 我的想法是为每个函数的调用签名创建一个接口,然后从那里开始。您认为什么可行?

编辑:

我的第一个答案很有效,问题是当我传递特定 class 的函数时,它丢失了已经初始化的 class 的范围和属性丢失他们的价值观。

private async executeWithRetry < T > (command: ICommand < T > , policy: RetryPolicy, api: WebApi) {
  while (true) {
    try {
      policy.incrementTry();
      return await command.execute(api);
    } catch (err) {
      if (policy.shouldRetry(err)) {
        console.log("Request failed: ", command.fnName, ", retry #", policy.currentTry())
        await new Promise((r) => setTimeout(r, policy.currentWait()));
      } else {
        console.log("Out of retries!!!")
        throw err; // Tell the humans!
      }
    }
  }
}

class PaginatedCommand < T > implements ICommand < T > {
  constructor(private fn: (options ? : {
    limit ? : number,
    offset ? : number
  }) => Promise < T > , private options: {
    limit: number,
    offset: number
  }) {}

  public async execute(api: WebApi): Promise < T > {
    //@ts-ignore
    return await api[this.fn.name](this.options);
  }

  public get fnName(): string {
    return this.fn.name;
  }
}


class PlaylistPaginatedCommand < T > implements ICommand < T > {
  constructor(private fn: (Id: string, options ? : {
    limit ? : number,
    offset ? : number
  }) => Promise < T > , private Id: string, private options: {
    limit: number,
    offset: number
  }) {}

  public async execute(api: WebApi): Promise < T > {
    //@ts-ignore
    return await api[this.fn.name](this.Id, this.options);
  }

  public get fnName(): string {
    return this.fn.name;
  }
}

您必须从传递的函数类型中提取实际类型。

这对我有用:

async function callWithRetry<T extends (...args: A) => Promise<R>, R, A extends Array<any>>(
    functionCall: (...args: A) => Promise<R>,
    args: Parameters<T>, 
    retries = 0
): Promise<R> {
    try {
        return await functionCall(...args);
    } catch (error: any) {
        if (retries <= 10) {
            if (error && error.statusCode === 429) {
                const retryAfter = (parseInt(error.headers['retry-after'] as string, 10) + 1) * 1000;
                console.log(`sleeping for: ${retryAfter.toString()}`);
                await new Promise((r) => setTimeout(r, retryAfter));
            }
            return await callWithRetry(functionCall, args, retries + 1);
        } else {
            throw error;
        }
    }
};

并且 TS 认识到,args 必须是正确的类型:

async function getUser(id: string) {
    return { id, name: "dummy" };
}

const x = callWithRetry(getUser, ['id']); // ok
const y = callWithRetry(getUser, ['id', 5]); // error

Typescript playground