如何在保持输入的同时使用 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
我有一个 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