Skip to content

Commit 11e2354

Browse files
authored
docs: Various improvements (#18)
1 parent c893e56 commit 11e2354

File tree

1 file changed

+38
-26
lines changed

1 file changed

+38
-26
lines changed

README.md

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ This package:
77
+ Uses the modern, standardized `fetch` function.
88
+ Does **not** throw on non-OK HTTP responses.
99
+ **Allows to fully type all possible HTTP responses depending on the HTTP status code.**
10+
+ Probably the tiniest fetch wrapper you'll ever need.
1011

1112
## Does a Non-OK Status Code Warrant an Error?
1213

13-
No. Non-OK status codes may communicate things like validation errors, which usually requires that the application
14-
*informs* the user about which piece of data is wrong. Why should this logic be inside a `catch` block? The fact is,
15-
`try..catch` would be there as a replacement of branching (using `if` or `switch`). This is a very smelly code smell.
14+
The short story is:
1615

17-
The second reason is that in most runtimes, unwinding the call stack is costly. Why should we pay a price in
18-
performance just to include the code smell of using `try..catch` as a branching statement? There is no reason to do
19-
such thing.
16+
1. Wrappers like `axios` or `ky` do `if (!response.ok) throw ...`, which forces code to `try..catch`. This is a code
17+
smell: `try..catch` is being used as a branching mechanism.
18+
2. The performance drop is huge. [See this benchmark](https://jsperf.app/dogeco). Over 40% loss.
19+
20+
[The issue of fetch wrappers explained in more detail](https://webjose.hashnode.dev/the-ugly-truth-all-popular-fetch-wrappers-do-it-wrong)
2021

2122
## Quickstart
2223

@@ -39,16 +40,16 @@ do is to add the `authorization` header and the `accept` header to every call.
3940

4041
```typescript
4142
// myFetch.ts
42-
import { obtainToken } from './magical-auth-stuff.js';
43-
import { setHeaders, type FetchFnUrl, type FetchFnInit } from 'dr-fetch';
43+
import { obtainToken } from "./magical-auth-stuff.js";
44+
import { setHeaders, type FetchFnUrl, type FetchFnInit } from "dr-fetch";
4445

4546
export function myFetch(url: FetchFnUrl, init?: FetchFnInit) {
4647
const token = obtainToken();
4748
// Make sure there's an object where headers can be added:
4849
init ??= {};
4950
// With setHeaders(), you can add headers to 'init' with a map, an array of tuples, a Headers
5051
// object or a POJO object.
51-
setHeaders(init, { 'Accept': 'application/json', 'Authorization': `Bearer ${token}`});
52+
setHeaders(init, { Accept: 'application/json', Authorization: `Bearer ${token}`});
5253
// Finally, do fetch.
5354
return fetch(url, init);
5455
}
@@ -95,7 +96,8 @@ export default new DrFetch(myFetch)
9596
;
9697
```
9798

98-
> **NOTE**: The content type can also be matched passing a regular expression instead of a string.
99+
> [!NOTE]
100+
> The content type can also be matched passing a regular expression instead of a string.
99101
100102
Now the fetcher object is ready for use.
101103

@@ -105,7 +107,7 @@ This is the fun part where we can enumerate the various shapes of the body depen
105107

106108
```typescript
107109
import type { MyData } from "./my-datatypes.js";
108-
import fetcher from './fetcher.js';
110+
import fetcher from "./fetcher.js";
109111

110112
const response = await fetcher
111113
.for<200, MyData[]>()
@@ -173,15 +175,21 @@ function specialFetch(url: FetchFnUrl, init?: FetchFnInit) {
173175
...
174176
}
175177

176-
const localFetcher = rootFetcher.clone(true); // Same data-fetching function, body processors and body typing.
177-
const localFetcher = rootFetcher.clone(false); // Same data-fetching function and body processors. No body typing.
178-
const localFetcher = rootFetcher.clone(true, { fetchFn: specialFetch }); // Different data-fetching function.
179-
const localFetcher = rootFetcher.clone(true, { includeProcessors: false }); // No custom body processors.
180-
const localFetcher = rootFetcher.clone(true, { fetchFn: false }); // Identical processors and body typing, stock fetch().
178+
// Same data-fetching function, body processors and body typing.
179+
const specialFetcher = rootFetcher.clone(true);
180+
// Same data-fetching function and body processors. No body typing.
181+
const specialFetcher = rootFetcher.clone(false);
182+
// Different data-fetching function.
183+
const specialFetcher = rootFetcher.clone(true, { fetchFn: specialFetch });
184+
// No custom body processors.
185+
const specialFetcher = rootFetcher.clone(true, { includeProcessors: false });
186+
// Identical processors and body typing, stock fetch().
187+
const specialFetcher = rootFetcher.clone(true, { fetchFn: false });
181188
```
182189

183-
> **IMPORTANT**: The first parameter to the `clone` function cannot be a variable. It is just used as a TypeScript
184-
> trick to reset the body typing. The value itself means nothing in runtime because types are not a runtime thing.
190+
> [!IMPORTANT]
191+
> The first parameter to the `clone` function cannot be a variable. It is just used as a TypeScript trick to reset the
192+
> body typing. The value itself means nothing in runtime because types are not a runtime thing.
185193
186194
## Shortcut Functions
187195

@@ -194,7 +202,7 @@ the `Content-Type` header is given the value `application/json`. If a body of a
194202
`fetch()` (or the custom data-fetching function you provide) does in these cases.
195203

196204
```typescript
197-
import type { Todo } from './myTypes.js';
205+
import type { Todo } from "./myTypes.js";
198206

199207
const newTodo = { text: 'I am new. Insert me!' };
200208
const response = await fetcher
@@ -253,7 +261,9 @@ export function myFetch(URL: FetchFnUrl, init?: FetchFnInit) {
253261
}
254262
```
255263

256-
This would also get more complex if you account for multi-value headers. Now the same thing, using `setHeaders()`:
264+
This would also get more complex if you account for multi-value headers.
265+
266+
Now the same thing, using `setHeaders()`:
257267

258268
```typescript
259269
import type { FetchFnUrl, FetchFnInit } from "dr-fetch";
@@ -271,8 +281,8 @@ export function myFetch(URL: FetchFnUrl, init?: FetchFnInit) {
271281
}
272282
```
273283

274-
The difference is indeed pretty shocking. Also note that adding arrays of values doesn't increase the complexity of
275-
the code.
284+
The difference is indeed pretty shocking: One line of code and you are done. Also note that adding arrays of values
285+
doesn't increase the complexity of the code: It's still one line.
276286

277287
### makeIterableHeaders
278288

@@ -300,7 +310,7 @@ const myHeaders4 = [
300310
['Authorization', 'Bearer x'],
301311
];
302312

303-
// The output of these is identical.
313+
// The output of all these is identical.
304314
console.log([...makeIterableHeaders(myHeaders1)]);
305315
console.log([...makeIterableHeaders(myHeaders2)]);
306316
console.log([...makeIterableHeaders(myHeaders3)]);
@@ -331,6 +341,8 @@ a simple class and a custom body processor.
331341
The following is a class for **Svelte v5**. It contains a reactive `progress` property that is updated as download
332342
progresses.
333343

344+
[Live demo in the Svelte REPL](https://svelte.dev/playground/ddeedfb44ab74727ac40df320c552b92?version=5.25.3)
345+
334346
> [!NOTE]
335347
> You should have no problems translating this to Vue, SolidJS or even Angular since all these are signal-powered.
336348
> For React, you'll have to get rid of the signals part and perhaps make it callback-powered.
@@ -368,7 +380,7 @@ Create a custom processor for the content type that will be received, for exampl
368380

369381
```ts
370382
// downloader.ts
371-
import { DownloadProgress } from './DownloadProgress.svelte.js';
383+
import { DownloadProgress } from "./DownloadProgress.svelte.js";
372384

373385
export default new DrFetch(/* custom fetch function here, if needed */)
374386
.withProcessor('video/mp4', (r) => Promise.resolve(new DownloadProgress(r)))
@@ -380,8 +392,8 @@ carry the class instance in the `body` property.
380392

381393
```svelte
382394
<script lang="ts">
383-
import { DownloadProgress } from './DownloadProgress.svelte.js';
384-
import downloader from './downloader.js';
395+
import { DownloadProgress } from "./DownloadProgress.svelte.js";
396+
import downloader from "./downloader.js";
385397
386398
let download = $state<Download>();
387399

0 commit comments

Comments
 (0)