@@ -8,6 +8,7 @@ This package:
88+ Uses the modern, standardized ` fetch ` function.
99+ Does ** not** throw on non-OK HTTP responses.
1010+ ** Can fully type all possible HTTP responses depending on the HTTP status code, even non-standard ones like 499.**
11+ + ** Supports abortable HTTP requests; no boilerplate.**
1112+ Works in any runtime that implements ` fetch() ` (browsers, NodeJS, etc.).
1213+ Probably the tiniest fetch wrapper you'll ever need.
1314
@@ -105,7 +106,7 @@ Now the fetcher object is ready for use.
105106This is the fun part where we can enumerate the various shapes of the body depending on the HTTP status code:
106107
107108``` typescript
108- import type { MyData } from " ./my-datatypes .js" ;
109+ import type { MyData } from " ./my-types .js" ;
109110import fetcher from " ./fetcher.js" ;
110111
111112const response = await fetcher
@@ -117,6 +118,7 @@ const response = await fetcher
117118
118119The object stored in the ` response ` variable will contain the following properties:
119120
121+ + ` aborted ` : Will be ` false ` (since ** v0.8.0** )
120122+ ` ok ` : Same as ` Response.ok ` .
121123+ ` status ` : Same as ` Response.status ` .
122124+ ` statusText ` : Same as ` Response.statusText ` .
@@ -154,6 +156,69 @@ export default new DrFetch<MyStatusCode>();
154156
155157You will now be able to use non-standardized status code ` 499 ` to type the response body with ` DrFetch.for<>() ` .
156158
159+ ## Abortable HTTP Requests
160+
161+ > Since ** v0.8.0**
162+
163+ To create abortable HTTP requests, as per the standard, use an ` AbortController ` . The following is how you would have
164+ to write your code * without* ` dr-fetch ` :
165+
166+ ``` typescript
167+ const ac = new AbortController ();
168+ let aborted = false ;
169+ let response: Response ;
170+
171+ try {
172+ response = await fetch (' /url' , { signal: ac .signal });
173+ }
174+ catch (err ) {
175+ if (err instanceof DOMException && err .name === ' AbortError' ) {
176+ aborted = true ;
177+ }
178+ // Other stuff for non-aborted scenarios.
179+ }
180+ if (! aborted ) {
181+ const body = await response .json ();
182+ ...
183+ }
184+ ```
185+
186+ In contrast, using an abortable fetcher from ` dr-fetch ` , you reduce your code to:
187+
188+ ``` typescript
189+ // abortable-fetcher.ts
190+ import { DrFetch } from " dr-fetch" ;
191+
192+ export const abortableFetcher = new DrFetch ()
193+ .abortable ();
194+ ```
195+
196+ ``` typescript
197+ // some-component.ts
198+ import { abortableFetcher } from " ./abortable-fetcher.js" ;
199+
200+ const ac = new AbortController ();
201+
202+ const response = await abortableFetcher
203+ .for < 200 , MyData []> (),
204+ .for < 400 , ValidationError []> ()
205+ .get (' /url' , { signal: ac .signal });
206+ if (! response .aborted ) {
207+ ...
208+ }
209+ ```
210+
211+ In short: All boilerplate is gone. Your only job is to create the abort controller, pass the signal and after
212+ awaiting for the response, you check the value of the ` aborted ` property.
213+
214+ TypeScript and Intellisense will be fully accurate: If ` response.aborted ` is true, then the ` response.error ` property
215+ is available; otherwise the usual ` ok ` , ` status ` , ` statusText ` and ` body ` properties will be the ones available.
216+
217+ For full details and feedback on this feature, see [ this discussion] ( https://github.com/WJSoftware/dr-fetch/discussions/25 ) .
218+
219+ > [ !IMPORTANT]
220+ > Calling ` DrFetch.abortable() ` permanently changes the fetcher object's configuration.
221+
157222## Smarter Uses
158223
159224It is smart to create just one fetcher, configure it, then use it for every fetch call. Because generally speaking,
@@ -164,7 +229,7 @@ if your API is standardized so all status `400` bodies look the same? Then conf
164229// root-fetcher.ts
165230import { DrFetch } from " dr-fetch" ;
166231import { myFetch } from " ./my-fetch.js" ;
167- import type { BadRequestBody } from " my-datatypes .js" ;
232+ import type { BadRequestBody } from " my-types .js" ;
168233
169234export default new DrFetch (myFetch )
170235 .withProcessor (... ) // Optional processors
@@ -175,14 +240,39 @@ export default new DrFetch(myFetch)
175240
176241You can now consume this root fetcher object and it will be pre-typed for the ` 400 ` status code.
177242
243+ ### About Abortable Fetchers
244+
245+ > Since ** v0.8.0**
246+
247+ If your project has a need for abortable and non-abortable fetcher objects, the smarter option would be to create and
248+ export 2 fetcher objects, instead of one root fetcher:
249+
250+ ``` typescript
251+ // root-fetchers.ts
252+ import { DrFetch } from " dr-fetch" ;
253+ import { myFetch } from " ./my-fetch.js" ;
254+ import type { BadRequestBody } from " my-types.js" ;
255+
256+ export const rootFetcher new DrFetch(myFetch)
257+ .withProcessor (... ) // Optional processors
258+ .withProcessor (... )
259+ .for < 400 , BadRequestBody > ()
260+ ;
261+
262+ export const abortableRootFetcher = rootFetcher .clone ().abortable ();
263+ ```
264+
265+ We clone it because ` abortable() ` has permanent side effects on the object's state. Cloning also helps with other
266+ scenarios, as explained next.
267+
178268### Specializing the Root Fetcher
179269
180- Ok, nice, but what if we needed a custom processor for just one particular URL? It makes no sense to add it to the
181- root fetcher, and maybe it is even harmful to do so. In that case , clone the fetcher.
270+ What if we needed a custom processor for just one particular URL? It makes no sense to add it to the root fetcher, and
271+ maybe it is even harmful to do so. In cases like this one , clone the fetcher.
182272
183- Cloning a fetcher produces a new fetcher with the same data-fetching function, the same body processors and the same
184- body typings, ** unless** we specify we want something different, like not cloning the body types, or specifying a new
185- data-fetching function.
273+ Cloning a fetcher produces a new fetcher with the same data-fetching function, the same body processors, the same
274+ support for abortable HTTP requests and the same body typings, ** unless** we specify we want something different, like
275+ not cloning the body types, or specifying a new data-fetching function.
186276
187277``` typescript
188278import rootFetcher from " ./root-fetcher.js" ;
@@ -192,31 +282,38 @@ function specialFetch(url: FetchFnUrl, init?: FetchFnInit) {
192282 ...
193283}
194284
195- // Same data-fetching function, body processors and body typing.
196- const specialFetcher = rootFetcher .clone (true );
197- // Same data-fetching function and body processors. No body typing.
198- const specialFetcher = rootFetcher .clone (false );
199- // Different data-fetching function.
200- const specialFetcher = rootFetcher .clone (true , { fetchFn: specialFetch });
201- // No custom body processors.
202- const specialFetcher = rootFetcher .clone (true , { includeProcessors: false });
203- // Identical processors and body typing, stock fetch().
204- const specialFetcher = rootFetcher .clone (true , { fetchFn: false });
285+ // Same data-fetching function, body processors, abortable support and body typing.
286+ const specialFetcher = rootFetcher .clone ();
287+ // Same data-fetching function, abortable support and body processors; no body typing.
288+ const specialFetcher = rootFetcher .clone ({ preserveTyping: false });
289+ // Same everything; different data-fetching function.
290+ const specialFetcher = rootFetcher .clone ({ fetchFn: specialFetch });
291+ // Same everything; no custom body processors.
292+ const specialFetcher = rootFetcher .clone ({ includeProcessors: false });
293+ // Identical processors, abortable support and body typing; stock fetch().
294+ const specialFetcher = rootFetcher .clone ({ fetchFn: false });
295+ // Identical processors, body typing and fetch function; no abortable support (the default when constructing).
296+ const specialFetcher = rootFetcher .clone ({ preserveAbortable: false });
205297```
206298
207299> [ !IMPORTANT]
208- > The first parameter to the ` clone ` function cannot be a variable. It is just used as a TypeScript trick to reset the
209- > body typing. The value itself means nothing in runtime because types are not a runtime thing.
300+ > ` preserveTyping ` is a TypeScript trick and cannot be a variable of type ` boolean ` . Its value doesn't matter in
301+ > runtime because types are not a runtime thing, and TypeScript depends on knowing if the value is ` true ` or ` false ` .
302+ >
303+ > On the other hand, ` preserveAbortable ` is a hybrid: It uses the same TypeScript trick, but its value does matter in
304+ > runtime because an abortable fetcher object has different inner state than a stock fetcher object. In this sense,
305+ > supporting a variable would be ideal, but there's just no way to properly reconcile the TypeScript side with a
306+ > variable of type ` boolean ` . Therefore, try to always use constant values.
210307
211308## Shortcut Functions
212309
213310> Since ** v0.3.0**
214311
215312` DrFetch ` objects now provide the shortcut functions ` get ` , ` head ` , ` post ` , ` patch ` , ` put ` and ` delete ` . Except for
216- ` get ` and ` head ` , all these accept a body parameter. When this body is a POJO or an array, the body is stringified and
217- the ` Content-Type ` header is given the value ` application/json ` . If a body of any other type is given (that the
218- ` fetch() ` function accepts, such as ` FormData ` ), no headers are explicitly specified and therefore it is up to what
219- ` fetch() ` (or the custom data-fetching function you provide) does in these cases.
313+ ` get ` and ` head ` , all these accept a body parameter. When this body is a POJO or an array, the body is stringified
314+ and, if no explicit ` Content-Type ` header is set, the ` Content-Type ` header is given the value ` application/json ` . If
315+ a body of any other type is given (that the ` fetch() ` function accepts, such as ` FormData ` ), no headers are explicitly
316+ added and therefore it is up to what ` fetch() ` (or the custom data-fetching function you provide) does in these cases.
220317
221318``` typescript
222319import type { Todo } from " ./myTypes.js" ;
@@ -237,6 +334,20 @@ const response = await fetcher
237334As stated, your custom fetch can be used to further customize the request because these shortcut functions will, in the
238335end, call it.
239336
337+ ### Parameters
338+
339+ > Since ** v0.8.0**
340+
341+ The ` get ` and ` head ` shortcut functions' parameters are:
342+
343+ ` (url: URL | string, init?: RequestInit) `
344+
345+ The other shortcut functions' parameters are:
346+
347+ ` (url: URL | string, body?: BodyInit | null | Record<string, any>, init?: RequestInit) `
348+
349+ Just note that ` init ` won't accept the ` method ` or ` body ` properties (the above is a simplification).
350+
240351## setHeaders and makeIterableHeaders
241352
242353> Since ** v0.4.0**
@@ -347,6 +458,18 @@ for (let [key, value] of makeIterableHeaders(myHeaders)) { ... }
347458expect ([... makeIterableHeaders (myHeaders )].length ).to .equal (2 );
348459```
349460
461+ ## hasHeader and getHeader
462+
463+ These are two helper functions that do exactly what the names imply: ` hasHeader ` checks for the existence of a
464+ particular HTTP header; ` getHeader ` obtains the value of a particular HTTP header.
465+
466+ These functions perform a sequential search with the help of ` makeIterableHeaders ` .
467+
468+ > [ !NOTE]
469+ > Try not to use ` getHeader ` to determine the existence of a header ** without** having the following in mind: The
470+ > function returns ` undefined ` if the value is not found, but it could return ` undefined ` if the header is found * and*
471+ > its value is ` undefined ` .
472+
350473## Usage Without TypeScript (JavaScript Projects)
351474
352475Why are you a weird fellow/gal? Anyway, prejudice aside, body typing will mean nothing to you, so forget about ` for() `
0 commit comments