Skip to content

Instantly share code, notes, and snippets.

@afruzan
Last active April 17, 2023 14:21
Show Gist options
  • Save afruzan/8f21ab03b1deec2b74b562ed1a30486b to your computer and use it in GitHub Desktop.
Save afruzan/8f21ab03b1deec2b74b562ed1a30486b to your computer and use it in GitHub Desktop.
JavaScript promise retry policy and delay with cancellation token
export class RetryPolicy<T> {
constructor(
private action: () => Promise<T>,
private retryDelays: number[]) {
}
private retryNumber;
private cancellationToken: CancellationToken;
public async run(): Promise<T> {
this.retryNumber = -1;
this.cancellationToken = new CancellationToken();
return this.run_internal(this.cancellationToken);
}
public cancel() {
this.cancellationToken.cancel();
}
private async run_internal(cancellationToken: CancellationToken): Promise<T> {
try {
this.retryNumber += 1;
return await this.action();
} catch (error) {
if (this.retryNumber <= this.retryDelays.length) {
if (cancellationToken.isCanceled) {
throw new RetryCanceledError(error, 'more retries canceled for the action.');
}
try {
const ms = this.retryDelays[this.retryNumber];
console.warn('action failed. waiting for ' + ms + ' ms before retrying...', error);
await delay(ms + 1, cancellationToken);
} catch (error) {
if (error instanceof PromiseCanceledError) {
throw new RetryCanceledError(error, 'more retries canceled for the action.');
}
else {
throw error;
}
}
if (cancellationToken.isCanceled) {
throw new RetryCanceledError(error, 'more retries canceled for the action.');
}
console.log('retrying the action...');
return this.run_internal(cancellationToken);
}
else {
throw error;
}
}
}
}
export class RetryCanceledError extends Error {
constructor(public error: Error, message?: string) {
super(message)
}
}
function delay(ms, cancellationToken?: CancellationToken): Promise<void> {
return new Promise<void>((resolve, reject) => {
const t = setTimeout(() => resolve(), ms);
if (cancellationToken) {
cancellationToken.cancel = () => {
cancellationToken.isCanceled = true;
clearTimeout(t);
reject(new PromiseCanceledError('delay cancelled.'));
};
}
});
}
export class CancellationToken {
isCanceled: boolean = false;
cancel() {
this.isCanceled = true;
};
}
export class PromiseCanceledError extends Error {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment