wretch
Version:
A tiny wrapper built around fetch with an intuitive syntax.
1 lines • 7.66 kB
Source Map (JSON)
{"version":3,"file":"retry.min.cjs","names":[],"sources":["../../../src/middlewares/retry.ts"],"sourcesContent":["import type { ConfiguredMiddleware, WretchOptions } from \"../types.js\"\n\n/* Types */\n\nexport type DelayRampFunction = (delay: number, nbOfAttempts: number) => number\nexport type UntilFunction = (\n response?: Response,\n error?: Error\n) => boolean | Promise<boolean>\nexport type OnRetryFunctionResponse =\n | { url?: string; options?: WretchOptions }\n | undefined\nexport type OnRetryFunction = (args: {\n response?: Response\n error?: Error\n url: string\n attempt: number\n options: WretchOptions\n}) => void | OnRetryFunctionResponse | Promise<OnRetryFunctionResponse>\nexport type SkipFunction = (url: string, opts: WretchOptions) => boolean\nexport type RetryOptions = {\n /**\n * The timer between each attempt in milliseconds.\n *\n * _Default: `500`_\n */\n delayTimer?: number\n /**\n * The custom function that is used to calculate the actual delay based on the the timer & the number of attemps.\n *\n * _Default: `delay * nbOfAttemps`_\n */\n delayRamp?: DelayRampFunction\n /**\n * The maximum number of retries before resolving the promise with the last error. Specifying 0 means infinite retries.\n *\n * _Default: `10`_\n */\n maxAttempts?: number\n /**\n * The request will be retried until that condition is satisfied.\n *\n * _Default: `response && (response.ok || (response.status >= 400 && response.status < 500))`_\n */\n until?: UntilFunction\n /**\n * Callback that will get executed before retrying the request. If this function returns an object having url and/or options properties, they will override existing values in the retried request.\n *\n * The callback receives an object with: `response`, `error`, `url`, `attempt` (current attempt number, starting at 1), and `options`.\n *\n * _Default: `undefined`_\n */\n onRetry?: OnRetryFunction\n /**\n * If true, will retry the request if a network error was thrown. Will also provide an 'error' argument to the `onRetry` and `until` methods.\n *\n * _Default: `false`_\n */\n retryOnNetworkError?: boolean\n /**\n * If true, the request will be resolved with the latest response instead of rejected with an error.\n *\n * _Default: `false`_\n */\n resolveWithLatestResponse?: boolean\n /**\n * If skip returns true, the request will not be retried.\n *\n * Example:\n * ```js\n * (url, options) => (\n * options.method !== \"GET\"\n * )\n * ```\n *\n * _Default: `undefined`_\n */\n skip?: SkipFunction\n}\n\n/**\n * ## Retry middleware\n *\n * #### Retries a request multiple times in case of an error (or until a custom condition is true).\n *\n * > **💡 By default, the request will be retried only for server errors (5xx) and other non-successful responses, but not for client errors (4xx).**\n * >\n * > ```js\n * > // To retry on all non-2xx responses (including 4xx):\n * > until: (response, error) => response && response.ok\n * > ```\n *\n * ```ts\n * import wretch from 'wretch'\n * import { retry } from 'wretch/middlewares'\n *\n * wretch().middlewares([\n * retry({\n * // Options - defaults below\n * delayTimer: 500,\n * delayRamp: (delay, nbOfAttempts) => delay * nbOfAttempts,\n * maxAttempts: 10,\n * until: (response, error) => response && (response.ok || (response.status >= 400 && response.status < 500)),\n * onRetry: null,\n * retryOnNetworkError: false,\n * resolveWithLatestResponse: false,\n * skip: undefined\n * })\n * ])\n *\n * // You can also return a Promise, which is useful if you want to inspect the body:\n * wretch().middlewares([\n * retry({\n * until: response =>\n * response.clone().json().then(body =>\n * body.field === 'something'\n * )\n * })\n * ])\n * ```\n */\nexport type RetryMiddleware = (options?: RetryOptions) => ConfiguredMiddleware\n\n/* Defaults */\n\nconst defaultDelayRamp: DelayRampFunction = (delay, nbOfAttempts) =>\n delay * nbOfAttempts\nconst defaultUntil: UntilFunction = response =>\n response && (response.ok || (response.status >= 400 && response.status < 500))\n\nexport const retry: RetryMiddleware = ({\n delayTimer = 500,\n delayRamp = defaultDelayRamp,\n maxAttempts = 10,\n until = defaultUntil,\n onRetry = null,\n retryOnNetworkError = false,\n resolveWithLatestResponse = false,\n skip,\n} = {}) => {\n return next => (url, opts) => {\n let attempts = 0\n\n if (skip && skip(url, opts)) {\n return next(url, opts)\n }\n\n const checkStatus = (response?: Response, error?: Error) => {\n return Promise.resolve(until(response, error)).then(done => {\n // If the response is not suitable\n if (!done) {\n attempts++\n\n if (!maxAttempts || attempts <= maxAttempts) {\n // We need to recurse until we have a correct response and chain the checks\n return new Promise(resolve => {\n const delay = delayRamp(delayTimer, attempts)\n setTimeout(() => {\n if (typeof onRetry === \"function\") {\n Promise.resolve(\n onRetry({\n response,\n error,\n url,\n attempt: attempts,\n options: opts,\n })\n ).then((values = {}) => {\n resolve(\n next(\n (values && values.url) ?? url,\n (values && values.options) ?? opts\n )\n )\n })\n } else {\n resolve(next(url, opts))\n }\n }, delay)\n })\n .then(checkStatus)\n .catch(error => {\n if (!retryOnNetworkError) throw error\n return checkStatus(null, error)\n })\n } else {\n return !!response && resolveWithLatestResponse\n ? response\n : Promise.reject(\n error || new Error(\"Number of attempts exceeded.\")\n )\n }\n }\n\n return !!response && resolveWithLatestResponse\n ? response\n : error\n ? Promise.reject(error)\n : response\n })\n }\n\n return next(url, opts)\n .then(checkStatus)\n .catch(error => {\n if (!retryOnNetworkError) throw error\n return checkStatus(null, error)\n })\n }\n}\n"],"mappings":"mEA6HA,MAAM,GAAuC,EAAO,IAClD,EAAQ,EACJ,EAA8B,GAClC,IAAa,EAAS,IAAO,EAAS,QAAU,KAAO,EAAS,OAAS,KAE9D,GAA0B,CACrC,aAAa,IACb,YAAY,EACZ,cAAc,GACd,QAAQ,EACR,UAAU,KACV,sBAAsB,GACtB,4BAA4B,GAC5B,QACE,EAAE,GACG,IAAS,EAAK,IAAS,CAC5B,IAAI,EAAW,EAEf,GAAI,GAAQ,EAAK,EAAK,EAAK,CACzB,OAAO,EAAK,EAAK,EAAK,CAGxB,IAAM,GAAe,EAAqB,IACjC,QAAQ,QAAQ,EAAM,EAAU,EAAM,CAAC,CAAC,KAAK,GAE7C,EA4CI,GAAY,EACjB,EACA,EACE,QAAQ,OAAO,EAAM,CACrB,GA/CJ,IAEI,CAAC,GAAe,GAAY,EAEvB,IAAI,QAAQ,GAAW,CAC5B,IAAM,EAAQ,EAAU,EAAY,EAAS,CAC7C,eAAiB,CACX,OAAO,GAAY,WACrB,QAAQ,QACN,EAAQ,CACN,WACA,QACA,MACA,QAAS,EACT,QAAS,EACV,CAAC,CACH,CAAC,MAAM,EAAS,EAAE,GAAK,CACtB,EACE,GACG,GAAU,EAAO,MAAQ,GACzB,GAAU,EAAO,UAAY,EAC/B,CACF,EACD,CAEF,EAAQ,EAAK,EAAK,EAAK,CAAC,EAEzB,EAAM,EACT,CACC,KAAK,EAAY,CACjB,MAAM,GAAS,CACd,GAAI,CAAC,EAAqB,MAAM,EAChC,OAAO,EAAY,KAAM,EAAM,EAC/B,CAEK,GAAY,EACjB,EACA,QAAQ,OACR,GAAa,MAAM,+BAA+B,CACnD,EASP,CAGJ,OAAO,EAAK,EAAK,EAAK,CACnB,KAAK,EAAY,CACjB,MAAM,GAAS,CACd,GAAI,CAAC,EAAqB,MAAM,EAChC,OAAO,EAAY,KAAM,EAAM,EAC/B"}