diff --git a/README.md b/README.md index acab78c3..97f64b76 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,7 @@ await ofetch('/url', { ignoreResponseError: true }) ## ✔️ Auto Retry -`ofetch` Automatically retries the request if an error happens and if response status code is included in `retryStatusCodes` list. - -Default is `1` retry, except for `POST`, `PUT`, `PATCH` and `DELETE` methods ofetch does not retry. +`ofetch` Automatically retries the request if an error happens and if response status code is included in `retryStatusCodes` list: **Retry status codes:** @@ -114,9 +112,16 @@ Default is `1` retry, except for `POST`, `PUT`, `PATCH` and `DELETE` methods ofe - `503` - Service Unavailable - `504` - Gateway Timeout +You can specifcy amount of retires and delay between them using `retry` and `retryDelay` options. + +Default for `retry` is `1` retry, except for `POST`, `PUT`, `PATCH` and `DELETE` methods ofetch does not retry. + +Default for `retryDelay` is zero ms. + ```ts await ofetch('http://google.com/404', { - retry: 3 + retry: 3, + retryDelay: 500 // ms }) ``` diff --git a/src/fetch.ts b/src/fetch.ts index 970633da..534dcca8 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -46,6 +46,9 @@ export interface FetchOptions response?: boolean; retry?: number | false; + /** Delay between retries in milliseconds. */ + retryDelay?: number; + onRequest?(context: FetchContext): Promise | void; onRequestError?( context: FetchContext & { error: Error } @@ -86,7 +89,7 @@ const retryStatusCodes = new Set([ export function createFetch(globalOptions: CreateFetchOptions): $Fetch { const { fetch, Headers } = globalOptions; - function onError(context: FetchContext): Promise> { + async function onError(context: FetchContext): Promise> { // Is Abort // If it is an active abort, it will not retry automatically. // https://developer.mozilla.org/en-US/docs/Web/API/DOMException#error_names @@ -103,6 +106,10 @@ export function createFetch(globalOptions: CreateFetchOptions): $Fetch { const responseCode = (context.response && context.response.status) || 500; if (retries > 0 && retryStatusCodes.has(responseCode)) { + const retryDelay = context.options.retryDelay || 0; + if (retryDelay > 0) { + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + } return $fetchRaw(context.request, { ...context.options, retry: retries - 1, diff --git a/test/index.test.ts b/test/index.test.ts index 05f1e9fb..5eceb631 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -61,7 +61,12 @@ describe("ofetch", () => { eventHandler(() => createError({ status: 403, statusMessage: "Forbidden" }) ) + ) + .use( + "/408", + eventHandler(() => createError({ status: 408 })) ); + listener = await listen(toNodeListener(app)); }); @@ -135,7 +140,7 @@ describe("ofetch", () => { const { headers } = await $fetch(getURL("post"), { method: "POST", body: { num: 42 }, - headers: sentHeaders, + headers: sentHeaders as HeadersInit, }); expect(headers).to.include({ "x-header": "1" }); expect(headers).to.include({ accept: "application/json" }); @@ -196,6 +201,20 @@ describe("ofetch", () => { expect(error.request).to.equal(getURL("404")); }); + it("retry with delay", async () => { + const slow = $fetch(getURL("408"), { + retry: 2, + retryDelay: 100, + }).catch(() => "slow"); + const fast = $fetch(getURL("408"), { + retry: 2, + retryDelay: 1, + }).catch(() => "fast"); + + const race = await Promise.race([slow, fast]); + expect(race).to.equal("fast"); + }); + it("abort with retry", () => { const controller = new AbortController(); async function abortHandle() {