I have a class which is basically accessing spotify data, and it needs some retry mechanism, i made a callWithRetry method, which calls a method with the passed args and does the retry's on every method from the 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
}
}
My problem here is that I can not use different length argument list functions, i would have to put the argument list of the functions in an object and have it typed as any, which makes me loose type safety. My idead was to make an interface for it for the call signatures of every function and go from there. What could work in your opinion?
EDIT:
I the first answer worked like a charm, the problem was that when i passed a function of a specific class it lost the scope and the properties of the class that were already initialised lost their values.
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;
}
}