Skip to content

Commit

Permalink
chore: handle native fetch timeout error (#5878)
Browse files Browse the repository at this point in the history
* chore: handle native fetch timeout error

* Type safe error name checks

* Abort signal immediately to fix race condition of tests

* Define signals in tests cases
  • Loading branch information
nflaig authored Aug 14, 2023
1 parent 5edda2b commit ca9535b
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 9 deletions.
21 changes: 19 additions & 2 deletions packages/api/src/utils/client/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function isFetchError(e: unknown): e is FetchError {
return e instanceof FetchError;
}

export type FetchErrorType = "failed" | "input" | "aborted" | "unknown";
export type FetchErrorType = "failed" | "input" | "aborted" | "timeout" | "unknown";

type FetchErrorCause = NativeFetchFailedError["cause"] | NativeFetchInputError["cause"];

Expand All @@ -42,6 +42,10 @@ export class FetchError extends Error {
super(`Request to ${url.toString()} was aborted`);
this.type = "aborted";
this.code = "ERR_ABORTED";
} else if (isNativeFetchTimeoutError(e)) {
super(`Request to ${url.toString()} timed out`);
this.type = "timeout";
this.code = "ERR_TIMEOUT";
} else {
super((e as Error).message);
this.type = "unknown";
Expand Down Expand Up @@ -120,6 +124,15 @@ type NativeFetchAbortError = DOMException & {
name: "AbortError";
};

/**
* ```
* DOMException [TimeoutError]: The operation was aborted due to timeout
* ```
*/
type NativeFetchTimeoutError = DOMException & {
name: "TimeoutError";
};

function isNativeFetchError(e: unknown): e is NativeFetchError {
return e instanceof TypeError && (e as NativeFetchError).cause instanceof Error;
}
Expand All @@ -133,5 +146,9 @@ function isNativeFetchInputError(e: unknown): e is NativeFetchInputError {
}

function isNativeFetchAbortError(e: unknown): e is NativeFetchAbortError {
return e instanceof DOMException && e.name === "AbortError";
return e instanceof DOMException && (e as NativeFetchAbortError).name === "AbortError";
}

function isNativeFetchTimeoutError(e: unknown): e is NativeFetchTimeoutError {
return e instanceof DOMException && (e as NativeFetchTimeoutError).name === "TimeoutError";
}
21 changes: 14 additions & 7 deletions packages/api/test/unit/client/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ describe("FetchError", function () {
id: string;
url?: string;
requestListener?: http.RequestListener;
abort?: true;
timeout?: number;
signalHandler?: () => AbortSignal;
errorType: FetchErrorType;
errorCode: string;
expectCause: boolean;
Expand Down Expand Up @@ -66,14 +65,24 @@ describe("FetchError", function () {
},
{
id: "Aborted request",
abort: true,
requestListener: () => {
// leave the request open until aborted
},
signalHandler: () => AbortSignal.abort(),
errorType: "aborted",
errorCode: "ERR_ABORTED",
expectCause: false,
},
{
id: "Timeout request",
requestListener: () => {
// leave the request open until timeout
},
signalHandler: () => AbortSignal.timeout(10),
errorType: "timeout",
errorCode: "ERR_TIMEOUT",
expectCause: false,
},
];

const afterHooks: (() => Promise<void>)[] = [];
Expand All @@ -90,7 +99,7 @@ describe("FetchError", function () {
});

for (const testCase of testCases) {
const {id, url = `http://localhost:${port}`, requestListener, abort} = testCase;
const {id, url = `http://localhost:${port}`, requestListener, signalHandler} = testCase;

it(id, async function () {
if (requestListener) {
Expand All @@ -107,9 +116,7 @@ describe("FetchError", function () {
);
}

const controller = new AbortController();
if (abort) setTimeout(() => controller.abort(), 20);
await expect(fetch(url, {signal: controller.signal})).to.be.rejected.then((error: FetchError) => {
await expect(fetch(url, {signal: signalHandler?.()})).to.be.rejected.then((error: FetchError) => {
expect(error.type).to.be.equal(testCase.errorType);
expect(error.code).to.be.equal(testCase.errorCode);
if (testCase.expectCause) {
Expand Down

0 comments on commit ca9535b

Please sign in to comment.