Skip to content

Commit

Permalink
use abort controller for timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
jd1378 committed Jun 11, 2021
1 parent 71dbe27 commit d032988
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 67 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

# Changelog

## v5.0.0

- fix timeout functionality as the abort signal is now supported as of deno v1.11

## v4.0.0

- due to adding `timeout` option and the way it works, it may cause issues. so I release this as breaking change.
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ This library offers a fetch wrapper that can:
- directly use objects for `body` as json (see below)
- send `form`, `formData`, `qs` (query string) easily from objects
- set a validator globally or per request, to reject when validator throws.
- accept a timeout option and reject on timeout (currently how it works leaks resources due to [AbortController](https://github.com/denoland/deno/issues/7019) not being supported yet)
- accept a timeout option and abort when timeout is reached
- add `Accept` header with value `application/json, text/plain, */*` if not already set by you

Version v5.0.0+ is the recommended version now (abort controller is used now). please don't use v4 of fetch goody anymore.

**Deno v1.11+ is required.**

## usage

you can import `wrapFetch` from `mod.ts` file.

```js
export { wrapFetch } from 'https://deno.land/x/fetch_goody@v4.0.0/mod.ts';
export { wrapFetch } from 'https://deno.land/x/fetch_goody@v5.0.0/mod.ts';
```

### wrapFetch
Expand Down
29 changes: 12 additions & 17 deletions fetch_wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as utils from "./utils.ts";
import { getHeader, setHeader } from "./header_utils.ts";
import { ExtendedRequestInit } from "./extended_request_init.ts";
import { timeoutFetch } from "./timeout_helper.ts";

/**
* Transforms data and adds corresponding headers if possible.
Expand Down Expand Up @@ -203,22 +202,18 @@ export function wrapFetch(options?: WrapFetchOptions) {
);
}

// ---------------- following will be uncommented when the mentioned issue is resolved
// let timeoutId: undefined | number;
// if (("timeout" in interceptedInit && interceptedInit.timeout) || timeout) {
// const abortController = new AbortController();
// timeoutId = setTimeout(
// abortController.abort,
// (interceptedInit as ExtendedRequestInit).timeout || timeout,
// );
// interceptedInit.signal = abortController.signal;
// }
// const response = await fetch(input, interceptedInit as RequestInit);

const response = await timeoutFetch(
(interceptedInit as ExtendedRequestInit).timeout || timeout,
fetch(input, interceptedInit as RequestInit),
);
let timeoutId: undefined | number;
if (("timeout" in interceptedInit && interceptedInit.timeout) || timeout) {
const abortController = new AbortController();
timeoutId = setTimeout(
() => abortController.abort(),
(interceptedInit as ExtendedRequestInit).timeout || timeout,
);
interceptedInit.signal = abortController.signal;
}

const response = await fetch(input, interceptedInit as RequestInit);
clearTimeout(timeoutId);

if (typeof validator === "function") {
await validator(response, interceptedInit);
Expand Down
95 changes: 48 additions & 47 deletions fetch_wrapper_test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
assert,
assertStrictEquals,
} from "https://deno.land/std@0.86.0/testing/asserts.ts";
import { serve, Server } from "https://deno.land/std@0.86.0/http/server.ts";
} from "https://deno.land/std@0.98.0/testing/asserts.ts";
import { serve, Server } from "https://deno.land/std@0.98.0/http/server.ts";
import { wrapFetch } from "./mod.ts";
import { delay } from "https://deno.land/std@0.86.0/async/delay.ts";
import { MultipartReader } from "https://deno.land/std@0.86.0/mime/multipart.ts";
import { delay } from "https://deno.land/std@0.98.0/async/delay.ts";
import { MultipartReader } from "https://deno.land/std@0.98.0/mime/multipart.ts";

let server1: Server | undefined;
const serverOneUrl = "http://localhost:54933";
Expand Down Expand Up @@ -91,14 +91,14 @@ async function closeServers() {
}
}

Deno.test({
name: "WrappedFetch global timeout option works",
fn: async () => {
Deno.test(
"WrappedFetch global timeout option works",
async () => {
try {
handlers.push(handleServer1());

const wrappedFetch = wrapFetch({
timeout: 1000,
timeout: 500,
});

let resp = await wrappedFetch(serverOneUrl + "/timeout", {
Expand Down Expand Up @@ -127,56 +127,57 @@ Deno.test({
}
assert(resp === "", "response should not be available");
assert(fetchError !== undefined, "timeout has not thrown");
assertStrictEquals(fetchError.message, "timeout");
assertStrictEquals(fetchError.message, "Ongoing fetch was aborted.");

// dummy fetch for letting the async ops finish
await (await fetch(serverOneUrl + "/accept")).text();
} finally {
await closeServers();
}
},
sanitizeOps: false,
});
);

Deno.test({
name: "WrappedFetch per request timeout option works",
fn: async () => {
try {
handlers.push(handleServer1());
Deno.test("WrappedFetch per request timeout option works", async () => {
try {
handlers.push(handleServer1());

const wrappedFetch = wrapFetch();
const wrappedFetch = wrapFetch();

let resp = await wrappedFetch(serverOneUrl + "/timeout", {
qs: {
ms: "0",
},
timeout: 1000,
}).then((r) => r.text());
let resp = await wrappedFetch(serverOneUrl + "/timeout", {
qs: {
ms: "0",
},
timeout: 500,
}).then((r) => r.text());

assertStrictEquals(
resp,
"ok",
);
assertStrictEquals(
resp,
"ok",
);

resp = "";
resp = "";

// see if it throws with timeout error
let fetchError;
try {
resp = await wrappedFetch(serverOneUrl + "/timeout", {
qs: {
ms: "2000",
},
timeout: 1000,
}).then((r) => r.text());
} catch (err) {
fetchError = err;
}
assert(resp === "", "response should not be available");
assert(fetchError !== undefined, "timeout has not thrown");
assertStrictEquals(fetchError.message, "timeout");
} finally {
await closeServers();
// see if it throws with timeout error
let fetchError;
try {
resp = await wrappedFetch(serverOneUrl + "/timeout", {
qs: {
ms: "2000",
},
timeout: 500,
}).then((r) => r.text());
} catch (err) {
fetchError = err;
}
},
sanitizeOps: false,
assert(resp === "", "response should not be available");
assert(fetchError !== undefined, "timeout has not thrown");
assertStrictEquals(fetchError.message, "Ongoing fetch was aborted.");

// dummy fetch for letting the async ops finish
await (await fetch(serverOneUrl + "/accept")).text();
} finally {
await closeServers();
}
});

Deno.test("WrappedFetch sends a default accept header", async () => {
Expand Down
2 changes: 1 addition & 1 deletion header_utils_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
assertNotStrictEquals,
assertStrictEquals,
equal,
} from "https://deno.land/std@0.85.0/testing/asserts.ts";
} from "https://deno.land/std@0.98.0/testing/asserts.ts";
import {
appendHeader,
deleteHeader,
Expand Down

0 comments on commit d032988

Please sign in to comment.