diff --git a/src/fetch.ts b/src/fetch.ts index 10c91583..f89fdf42 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -167,20 +167,26 @@ export function createFetch(globalOptions: CreateFetchOptions = {}): $Fetch { } let abortTimeout: NodeJS.Timeout | undefined; + let fetchOptions = context.options; + // The timeout signal is derived per attempt: reusing it across retries + // would forward an already aborted signal. if (context.options.timeout) { - context.options.signal = context.options.signal - ? AbortSignal.any([ - AbortSignal.timeout(context.options.timeout), - context.options.signal, - ]) - : AbortSignal.timeout(context.options.timeout); + fetchOptions = { + ...context.options, + signal: context.options.signal + ? AbortSignal.any([ + AbortSignal.timeout(context.options.timeout), + context.options.signal, + ]) + : AbortSignal.timeout(context.options.timeout), + }; } try { context.response = await fetch( context.request, - context.options as RequestInit + fetchOptions as RequestInit ); } catch (error) { context.error = error as Error; diff --git a/test/index.test.ts b/test/index.test.ts index 5ac20b07..2871c692 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -13,6 +13,7 @@ import { $fetch } from "../src/index.ts"; describe("ofetch", () => { let listener: ReturnType; + let timeoutOnceRequests = 0; const getURL = (url: string = "/") => listener.url! + (url.replace(/^\//, "") || ""); @@ -62,6 +63,15 @@ describe("ofetch", () => { resolve(new HTTPError({ status: 408 })); }, 1000 * 5); }); + }) + .all("/timeout-once", async () => { + timeoutOnceRequests++; + if (timeoutOnceRequests === 1) { + await new Promise((resolve) => { + setTimeout(resolve, 1000 * 5); + }); + } + return "ok"; }); listener = await serve(app, { port: 0, hostname: "localhost" }).ready(); @@ -344,6 +354,16 @@ describe("ofetch", () => { }); }); + it("retrying after a timeout", async () => { + const result = await $fetch(getURL("timeout-once"), { + timeout: 100, + retry: 1, + }); + + expect(result).to.equal("ok"); + expect(timeoutOnceRequests).to.equal(2); + }); + it("deep merges defaultOptions", async () => { const _customFetch = $fetch.create({ query: {