Last active
April 13, 2025 09:21
-
-
Save vitaly-t/6e3d285854d882b1618c7e435df164c4 to your computer and use it in GitHub Desktop.
retry-async
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Retry-status object type, for use with RetryCB. | |
*/ | |
export type RetryStatus<D = unknown> = { | |
/** | |
* Retry index, starting from 0. | |
*/ | |
readonly index: number, | |
/** | |
* Retry overall duration, in milliseconds. | |
*/ | |
readonly duration: number, | |
/** | |
* Last error, if available; | |
* it is undefined only when "retryAsync" calls "func" with index = 0. | |
*/ | |
readonly error?: Error, | |
/** | |
* Extra data for status handlers, if specified. | |
*/ | |
readonly data?: D | |
}; | |
/** | |
* Retry-status callback type. | |
*/ | |
export type RetryCB<T, D = unknown> = (s: RetryStatus<D>) => T; | |
/** | |
* Type for options passed into retryAsync function. | |
*/ | |
export type RetryOptions<D = unknown> = { | |
/** | |
* Maximum number of retries (infinite by default), | |
* or a callback to indicate the need for another retry. | |
*/ | |
readonly retry?: number | RetryCB<boolean, D>, | |
/** | |
* Retry delays, in milliseconds (no delay by default), | |
* or a callback that returns the delays. | |
*/ | |
readonly delay?: number | RetryCB<number, D>, | |
/** | |
* Error notifications. | |
*/ | |
readonly error?: RetryCB<void, D>, | |
/** | |
* Extra data for status handlers. | |
*/ | |
readonly data?: D | |
}; | |
/** | |
* Retries async operation returned from "func" callback, according to "options". | |
*/ | |
export function retryAsync<T, D>(func: RetryCB<Promise<T>, D>, options?: RetryOptions<D>): Promise<T> { | |
const start = Date.now(); | |
let index = 0, e: any; | |
let {retry = Number.POSITIVE_INFINITY, delay = -1, error, data} = options ?? {}; | |
const s = () => ({index, duration: Date.now() - start, error: e, data}); | |
const c = (): Promise<T> => func(s()).catch(err => { | |
e = err; | |
typeof error === 'function' && error(s()); | |
if ((typeof retry === 'function' ? (retry(s()) ? 1 : 0) : retry--) <= 0) { | |
return Promise.reject(e); | |
} | |
const d = typeof delay === 'function' ? delay(s()) : delay; | |
index++; | |
return d >= 0 ? (new Promise(a => setTimeout(a, d))).then(c) : c(); | |
}); | |
return c(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The most recent update added templated option
{data: D}
, which can be used as an extra parameter inside status handlers.For example, you can use it for implementing your own external retry-strategy, like shown below:
So above, if you pass optional
"http"
parameter intoretryAsyncStrategy
, it will execute a different retry strategy. It's just a more elegant way to abstract retry strategies away from the code that relies on them, through simple parametrization.UPDATE:
For the example above, it would make sense to replace the verbose
retryAsyncStrategy
with two simpler functions, as below: