@superutils/fetch 1.5.15 → 1.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -250
- package/dist/browser/index.min.js +2 -1
- package/dist/browser/index.min.js.map +1 -1
- package/dist/index.cjs +106 -32
- package/dist/index.d.cts +83 -184
- package/dist/index.d.ts +83 -184
- package/dist/index.js +103 -32
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -14,26 +14,25 @@ For full API reference check out the [docs page](https://alien45.github.io/super
|
|
|
14
14
|
|
|
15
15
|
- [Features](#features)
|
|
16
16
|
- [Installation](#installation)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
- [NPM](#npm)
|
|
18
|
+
- [CDN / Browser](#cdn--browser)
|
|
19
|
+
- [Defaults](#defaults)
|
|
20
|
+
- [Timeout](#timeout)
|
|
21
|
+
- [Conent Type](#content-type)
|
|
22
|
+
- [Response Parsing](#fetch-as)
|
|
23
23
|
- [Usage](#usage)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- [`fetchFunc`](#fetch-func): Using with third-party libraries (e.g., Axios)
|
|
24
|
+
- [`fetch()`](#fetch): drop-in replacement for built-in `fetch()`
|
|
25
|
+
- [`TimeoutPromise` Instance](#promise-features): finer control over the request
|
|
26
|
+
- [`Method Specific Functions`](#methods)
|
|
27
|
+
- [`fetch.get()`](#fetch-get)
|
|
28
|
+
- [`fetch.get.deferred()`](#fetch-deferred): cancellable and debounced or throttled `fetch()`
|
|
29
|
+
- [`fetch.post()`](#post): make post requests
|
|
30
|
+
- [`fetch.post.deferred()`](#post-deferred): cancellable and debounced or throttled `post()`
|
|
31
|
+
- [`Interceptors/Transformers`](#interceptors)
|
|
32
|
+
- [`Retry`](#retry) Retry on request failure
|
|
33
|
+
- [`Timeout`](#timeout) Abort request on timeout
|
|
34
|
+
- [`ApiClient`](#api-client): Isolated API client factory
|
|
35
|
+
- [`fetchFunc`](#fetch-func): Using with third-party libraries (e.g., Axios)
|
|
37
36
|
|
|
38
37
|
## Features
|
|
39
38
|
|
|
@@ -142,8 +141,8 @@ To retrieve the response in a different format (e.g., as text, a blob, or the ra
|
|
|
142
141
|
import fetch, { FetchAs } from '@superutils/fetch'
|
|
143
142
|
|
|
144
143
|
fetch
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
.get('[DUMMYJSON-DOT-COM]/products/1', { as: FetchAs.text })
|
|
145
|
+
.then(console.log)
|
|
147
146
|
```
|
|
148
147
|
|
|
149
148
|
> **Note:** To ensure type safety, the `as` property is excluded from `fetch.defaults` in TypeScript. Since this option determines the function's return type, setting it globally would prevent accurate type inference for individual requests.
|
|
@@ -160,8 +159,8 @@ Use as a drop-in replacement to built-in `fetch()`.
|
|
|
160
159
|
import fetch from '@superutils/fetch'
|
|
161
160
|
|
|
162
161
|
fetch('[DUMMYJSON-DOT-COM]/products/1')
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
.then(response => response.json())
|
|
163
|
+
.then(console.log)
|
|
165
164
|
```
|
|
166
165
|
|
|
167
166
|
<div id="promise-features"></div>
|
|
@@ -180,11 +179,11 @@ const request = fetch('[DUMMYJSON-DOT-COM]/products/1')
|
|
|
180
179
|
console.log(request.pending) // true
|
|
181
180
|
|
|
182
181
|
request.then(() => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
console.log(request.resolved) // true
|
|
183
|
+
console.log(request.pending) // false
|
|
184
|
+
console.log(request.rejected) // false
|
|
185
|
+
console.log(request.aborted) // false
|
|
186
|
+
console.log(request.timedout) // false
|
|
188
187
|
})
|
|
189
188
|
```
|
|
190
189
|
|
|
@@ -201,7 +200,7 @@ request.then(result => console.log(result), console.warn)
|
|
|
201
200
|
// Add a callback to do stuff whenever request is aborted externally.
|
|
202
201
|
// This will not be invoked if fetch fails or resolves (promise finalized naturally) using the Promise executor.
|
|
203
202
|
request.onEarlyFinalize.push((resolved, valueOrReason) =>
|
|
204
|
-
|
|
203
|
+
console.log('Aborted externally:', { resolved, valueOrReason }),
|
|
205
204
|
)
|
|
206
205
|
|
|
207
206
|
// resolve/reject before the promise is finalized
|
|
@@ -261,8 +260,8 @@ Equivalent to `fetch(url, { method: 'get', as: 'json' })`.
|
|
|
261
260
|
import fetch from '@superutils/fetch'
|
|
262
261
|
|
|
263
262
|
fetch
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
.get('[DUMMYJSON-DOT-COM]/products/1')
|
|
264
|
+
.then(product => console.log({ product }))
|
|
266
265
|
```
|
|
267
266
|
|
|
268
267
|
<div id="fetch-deferred"></div>
|
|
@@ -276,22 +275,22 @@ import fetch from '@superutils/fetch'
|
|
|
276
275
|
|
|
277
276
|
// Create a debounced search function with a 300ms delay.
|
|
278
277
|
const searchProducts = fetch.get.deferred({
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
delay: 300, // Debounce delay
|
|
279
|
+
resolveIgnored: 'WITH_UNDEFINED', // Ignored (aborted) promises will resolve with `undefined`
|
|
281
280
|
})
|
|
282
281
|
|
|
283
282
|
// User types 'iphone'
|
|
284
283
|
searchProducts('[DUMMYJSON-DOT-COM]/products/search?q=iphone').then(result => {
|
|
285
|
-
|
|
284
|
+
console.log('Result for "iphone":', result)
|
|
286
285
|
})
|
|
287
286
|
|
|
288
287
|
// Before 300ms has passed, the user continues typing 'iphone 12'
|
|
289
288
|
setTimeout(() => {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
289
|
+
searchProducts('[DUMMYJSON-DOT-COM]/products/search?q=iphone 12').then(
|
|
290
|
+
result => {
|
|
291
|
+
console.log('Result for "iphone 12":', result)
|
|
292
|
+
},
|
|
293
|
+
)
|
|
295
294
|
}, 200)
|
|
296
295
|
// Outcome:
|
|
297
296
|
// The first request for "iphone" is aborted.
|
|
@@ -306,18 +305,18 @@ setTimeout(() => {
|
|
|
306
305
|
- **`delay: 0`**: Disables debouncing and throttling, enabling sequential/queue mode. Both requests ("iphone"
|
|
307
306
|
and "iphone 12") would execute, but one after the other, never simultaneously.
|
|
308
307
|
- **`resolveIgnored` (enum)**: Controls how the promise for an aborted request (like the first "iphone" call) resolves.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
308
|
+
1. `ResolveIgnored.WITH_UNDEFINED` (used in the example): The promise for the aborted "iphone"
|
|
309
|
+
request resolves with `undefined`.
|
|
310
|
+
2. `ResolveIgnored.WITH_LAST`: The promise for the aborted "iphone" request waits and resolves with the result
|
|
311
|
+
of the final "iphone 12" request. Both promises resolve to the same value.
|
|
312
|
+
3. `ResolveIgnored.NEVER`: The promise for the aborted "iphone" request is neither resolved nor rejected.
|
|
313
|
+
It will remain pending indefinitely.
|
|
315
314
|
- **`resolveError` (enum)**: Controls how failed requests are handled.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
315
|
+
1. `ResolveError.NEVER`: The promise for a failed request will neither resolve nor reject, causing it to remain pending indefinitely.
|
|
316
|
+
> **Warning:** Use with caution, as this may lead to memory leaks if not handled properly.
|
|
317
|
+
2. `ResolveError.WITH_ERROR`: The promise resolves with the `FetchError` object instead of being rejected.
|
|
318
|
+
3. `ResolveError.WITH_UNDEFINED`: The promise resolves with an `undefined` value upon failure.
|
|
319
|
+
4. `ResolveError.REJECT`: (Default) The promise is rejected with a `FetchError`, adhering to standard promise behavior.
|
|
321
320
|
|
|
322
321
|
<!-- #### Using defaults to reduce redundancy
|
|
323
322
|
|
|
@@ -361,8 +360,8 @@ import fetch from '@superutils/fetch'
|
|
|
361
360
|
const newProduct = { title: 'Perfume Oil' }
|
|
362
361
|
|
|
363
362
|
fetch.post('[DUMMYJSON-DOT-COM]/products/add', newProduct).then(
|
|
364
|
-
|
|
365
|
-
|
|
363
|
+
createdProduct => console.log('Product created:', createdProduct),
|
|
364
|
+
error => console.error('Failed to create product:', error),
|
|
366
365
|
)
|
|
367
366
|
```
|
|
368
367
|
|
|
@@ -380,13 +379,13 @@ import PromisE from '@superutils/promise'
|
|
|
380
379
|
|
|
381
380
|
// Create a throttled function to auto-save product updates.
|
|
382
381
|
const saveProductThrottled = fetch.post.deferred(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
382
|
+
{
|
|
383
|
+
delay: 1000, // Throttle window of 1 second
|
|
384
|
+
throttle: true,
|
|
385
|
+
trailing: true, // Ensures the very last update is always saved
|
|
386
|
+
onResult: product => console.log(`[Saved] Product: ${product.title}`),
|
|
387
|
+
},
|
|
388
|
+
'[DUMMYJSON-DOT-COM]/products/add', // Default URL
|
|
390
389
|
)
|
|
391
390
|
// Simulate a user typing quickly, triggering multiple saves.
|
|
392
391
|
console.log('User starts typing...')
|
|
@@ -418,40 +417,40 @@ let currentRefreshToken = ''
|
|
|
418
417
|
// Create a debounced function to refresh the auth token.
|
|
419
418
|
// It waits 300ms after the last call before executing.
|
|
420
419
|
const requestNewToken = fetch.post.deferred(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
420
|
+
{
|
|
421
|
+
delay: 300, // debounce delay
|
|
422
|
+
onResult: ({ refreshToken = '' }) => {
|
|
423
|
+
console.log(
|
|
424
|
+
`Auth token successfully refreshed at ${new Date().toISOString()}`,
|
|
425
|
+
)
|
|
426
|
+
currentRefreshToken = refreshToken
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
'[DUMMYJSON-DOT-COM]/auth/refresh', // Default URL
|
|
430
|
+
() => ({
|
|
431
|
+
refreshToken: currentRefreshToken,
|
|
432
|
+
expiresInMins: 30,
|
|
433
|
+
}),
|
|
435
434
|
)
|
|
436
435
|
|
|
437
436
|
// First authenticate user to get the initial refresh token and then request new refresh tokens
|
|
438
437
|
fetch
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
438
|
+
.post<{ refreshToken: string }>(
|
|
439
|
+
'[DUMMYJSON-DOT-COM]/auth/login',
|
|
440
|
+
{
|
|
441
|
+
username: 'emilys',
|
|
442
|
+
password: 'emilyspass',
|
|
443
|
+
expiresInMins: 30,
|
|
444
|
+
},
|
|
445
|
+
{ credentials: 'include' },
|
|
446
|
+
)
|
|
447
|
+
.then(result => {
|
|
448
|
+
currentRefreshToken = result?.refreshToken
|
|
449
|
+
|
|
450
|
+
requestNewToken() // Called at 0ms
|
|
451
|
+
PromisE.delay(50, requestNewToken) // Called at 50ms
|
|
452
|
+
PromisE.delay(100, requestNewToken) // Called at 100ms
|
|
453
|
+
}, console.error)
|
|
455
454
|
// Outcome:
|
|
456
455
|
// The first two calls are aborted by the debounce mechanism.
|
|
457
456
|
// Only the final call executes, 300ms after it was made (at the 400ms mark).
|
|
@@ -467,8 +466,8 @@ The following interceptor callbacks allow intercepting and/or transforming at di
|
|
|
467
466
|
#### Interceptor types (executed in sequence):
|
|
468
467
|
|
|
469
468
|
1. `request`: Request interceptors are executed before a HTTP request is made.
|
|
470
|
-
|
|
471
|
-
|
|
469
|
+
- To transform the URL simply return a new or modified URL.
|
|
470
|
+
- To transform `fetch` options simply modify the options parameter
|
|
472
471
|
2. `response`: Response interceptors are executed after receiving a `fetch` Response regardless of the HTTP status code.
|
|
473
472
|
3. `result`: Result interceptors are executed before returning the result. To transform the result simply return a new value.
|
|
474
473
|
PS: if the value of `options.as` is `FetchAs.response` (`"response"`), the value received in result will be a `Response` object.
|
|
@@ -480,8 +479,8 @@ The following interceptor callbacks allow intercepting and/or transforming at di
|
|
|
480
479
|
- If an exception is raised while executing the interceptors, it will be gracefully ignored.
|
|
481
480
|
- Value returned (transformed) by an interceptor will be carried over to the subsequent interceptor of the same type.
|
|
482
481
|
- There are 2 category of interceptors:
|
|
483
|
-
|
|
484
|
-
|
|
482
|
+
- Local: interceptors provided when making a request.
|
|
483
|
+
- Global: interceptors that are executed application-wide on every request. Global interceptors can be added/accessed at `fetch.defaults.interceptors`. Global interceptors are always executed before local interceptors.
|
|
485
484
|
|
|
486
485
|
**Example: Interceptor usage**
|
|
487
486
|
|
|
@@ -489,50 +488,48 @@ The following interceptor callbacks allow intercepting and/or transforming at di
|
|
|
489
488
|
import fetch, { FetchError } from '@superutils/fetch'
|
|
490
489
|
|
|
491
490
|
const interceptors = {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
},
|
|
531
|
-
],
|
|
491
|
+
error: [
|
|
492
|
+
(err, url, options) => {
|
|
493
|
+
console.log('Request failed', err, url, options)
|
|
494
|
+
// return nothing/undefined to keep the error unchanged
|
|
495
|
+
// or return modified/new error
|
|
496
|
+
err.message = 'My custom error message!'
|
|
497
|
+
// or create a new FetchError by cloning it (make sure all the required properties are set correctly)
|
|
498
|
+
return err.clone('My custom error message!')
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
request: [
|
|
502
|
+
(url, options) => {
|
|
503
|
+
// add extra headers or modify request options here
|
|
504
|
+
options.headers.append('x-custom-header', 'some value')
|
|
505
|
+
|
|
506
|
+
// transform the URL by returning a modified URL
|
|
507
|
+
return url + '?param=value'
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
response: [
|
|
511
|
+
(response, url, options) => {
|
|
512
|
+
if (response.ok) return
|
|
513
|
+
console.log('request was not successful', { url, options })
|
|
514
|
+
|
|
515
|
+
// You can transform the response by returning different `Response` object or even make a completely new HTTP request.
|
|
516
|
+
// The subsequent response interceptors will receive the returned response
|
|
517
|
+
return fetch('[DUMMYJSON-DOT-COM]/products/1') // promise will be resolved automatically
|
|
518
|
+
},
|
|
519
|
+
],
|
|
520
|
+
result: [
|
|
521
|
+
(result, url, options) => {
|
|
522
|
+
const productId = Number(new URL(url).pathname.split('/products/')[1])
|
|
523
|
+
if (options.method === 'get' && !Number.isNaN(productId)) {
|
|
524
|
+
result.title ??= 'Unknown title'
|
|
525
|
+
}
|
|
526
|
+
return result
|
|
527
|
+
},
|
|
528
|
+
],
|
|
532
529
|
}
|
|
533
530
|
fetch
|
|
534
|
-
|
|
535
|
-
|
|
531
|
+
.get('[DUMMYJSON-DOT-COM]/products/1', { interceptors })
|
|
532
|
+
.then(product => console.log({ product }))
|
|
536
533
|
```
|
|
537
534
|
|
|
538
535
|
**Example: Add global request and error interceptors**
|
|
@@ -543,135 +540,131 @@ import fetch from '@superutils/fetch'
|
|
|
543
540
|
const { interceptors } = fetch.defaults
|
|
544
541
|
|
|
545
542
|
interceptors.request.push((url, options) => {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
543
|
+
// a headers to all requests make by the application
|
|
544
|
+
// add headers to all requests made by the application
|
|
545
|
+
options.headers.append('x-auth', 'token')
|
|
549
546
|
})
|
|
550
547
|
|
|
551
548
|
interceptors.error.push((err, url, options) => {
|
|
552
|
-
|
|
553
|
-
|
|
549
|
+
// log whenever a request fails
|
|
550
|
+
console.log('Error interceptor', err)
|
|
554
551
|
})
|
|
555
552
|
|
|
556
553
|
// Each time a requst is made using @superutils/fetch, the above interceptors will be executed when appropriate
|
|
557
554
|
fetch('[DUMMYJSON-DOT-COM]/products/1').then(console.log, console.warn)
|
|
558
555
|
```
|
|
559
556
|
|
|
560
|
-
<div id="retry"></div>
|
|
561
|
-
|
|
562
557
|
### Retry
|
|
563
558
|
|
|
559
|
+
<div id="retry"></div>
|
|
560
|
+
|
|
564
561
|
The `retry` option provides a robust mechanism to automatically re-attempt failed requests, with support for both linear and exponential backoff strategies to gracefully handle transient network issues.
|
|
565
562
|
|
|
566
563
|
```javascript
|
|
567
564
|
import fetch from '@superutils/fetch'
|
|
568
565
|
|
|
569
566
|
fetch
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
567
|
+
.get('[DUMMYJSON-DOT-COM]/products/1', {
|
|
568
|
+
retry: 3, // If request fails, retry up to three more times
|
|
569
|
+
// Retry on rate limits (429) or transient server errors (5xx).
|
|
570
|
+
retryIf: r => r.status === 429 || r.status >= 500,
|
|
571
|
+
})
|
|
572
|
+
.then(console.log)
|
|
576
573
|
```
|
|
577
574
|
|
|
578
|
-
|
|
575
|
+
### `ApiClient`: Isolated API client factory
|
|
579
576
|
|
|
580
|
-
|
|
577
|
+
<div id="api-client"></div>
|
|
581
578
|
|
|
582
|
-
|
|
579
|
+
A fully encapsulated and isolated API client factory designed to simplify creation of dedicated API clients with integrated request execution controls.
|
|
583
580
|
|
|
584
|
-
|
|
581
|
+
`ApiClient` creates a sandboxed environment for a specific API service. It provides complete isolation by ignoring global `fetch.defaults` by default, ensuring that instance-specific configurations remain clean and predictable. It bundles RESTful methods (`delete`, `get`, `head`, `options`, `patch`, `post`, `put`) and execution controls (debounce/throttle) into a single, cohesive unit.
|
|
585
582
|
|
|
586
|
-
|
|
587
|
-
import { createClient } from '@superutils/fetch'
|
|
583
|
+
#### Options Precedence & Merging
|
|
588
584
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
// common options (can be overridden)
|
|
597
|
-
headers: {
|
|
598
|
-
Authorization: 'Bearer my-secret-token',
|
|
599
|
-
'Content-Type': 'application/json',
|
|
600
|
-
},
|
|
601
|
-
timeout: 5000,
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
// defer options (can be overridden)
|
|
605
|
-
delay: 300,
|
|
606
|
-
retry: 2, // If request fails, retry up to two more times
|
|
607
|
-
},
|
|
608
|
-
)
|
|
585
|
+
- **Options follow a strict hierarchy**: `fixedOptions` > `call options` > `commonOptions`.
|
|
586
|
+
- Global `fetch.defaults` are ignored by default.
|
|
587
|
+
- **Headers**: Merged by key. Call-level headers override common headers with the same name.
|
|
588
|
+
- **Interceptors**: Cumulative. Interceptors execute sequentially (Common → Call → Fixed).
|
|
589
|
+
- **Error Messages**: Merged by key, allowing per-service customization without losing global messages.
|
|
609
590
|
|
|
610
|
-
|
|
611
|
-
apiClient('[DUMMYJSON-DOT-COM]/products/1', {
|
|
612
|
-
// The 'method' property cannot be overridden as it is used in the fixed options when creating the client.
|
|
613
|
-
// In TypeScript, the compiler will not allow this property.
|
|
614
|
-
// In Javascript, it will simply be ignored.
|
|
615
|
-
// method: 'post',
|
|
616
|
-
timeout: 3000, // The 'timeout' property can be overridden
|
|
617
|
-
}).then(console.log, console.warn)
|
|
618
|
-
|
|
619
|
-
// create a deferred client using "apiClient"
|
|
620
|
-
const deferredClient = apiClient.deferred(
|
|
621
|
-
{ retry: 0 }, // disable retrying by overriding the `retry` defer option
|
|
622
|
-
'[DUMMYJSON-DOT-COM]/products/1',
|
|
623
|
-
{ timeout: 3000 },
|
|
624
|
-
)
|
|
625
|
-
deferredClient({ timeout: 10000 }) // timeout is overridden by individual request
|
|
626
|
-
.then(console.log, console.warn)
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
<div id="create-post-client"></div>
|
|
630
|
-
|
|
631
|
-
### `createPostClient(fixedOptions, commonOptions, commonDeferOptions)`
|
|
591
|
+
#### Key Features
|
|
632
592
|
|
|
633
|
-
|
|
593
|
+
- **Isolation**: Instance-specific options scoped to the client and isolated from other instances.
|
|
594
|
+
- **Base Resolution**: Automatic path joining when `apiBaseUrl` is provided.
|
|
595
|
+
- **Unified Error Handling**: Optional `errorPrefix` to namespace errors for easier debugging.
|
|
596
|
+
- **Method Suite**: Integrated `delete`, `get`, `head`, `options`, `patch`, `post`, and `put` methods.
|
|
597
|
+
- **Deferred Variants**: All methods support `.deferred()` for debouncing, throttling, and sequential execution.
|
|
634
598
|
|
|
635
|
-
|
|
599
|
+
#### Example: Creating an API client
|
|
636
600
|
|
|
637
601
|
```javascript
|
|
638
|
-
import {
|
|
602
|
+
import { ApiClient } from '@superutils/fetch'
|
|
603
|
+
|
|
604
|
+
// Create a client for a specific API service
|
|
605
|
+
const productsClient = new ApiClient(
|
|
606
|
+
'https://dummyjson.com/api', // base URL
|
|
607
|
+
{
|
|
608
|
+
fixedOptions: {
|
|
609
|
+
// Options that cannot be overridden
|
|
610
|
+
headers: { Authorization: 'Bearer secret-token' },
|
|
611
|
+
},
|
|
612
|
+
commonOptions: {
|
|
613
|
+
// Default options (can be overridden per request)
|
|
614
|
+
timeout: 5000,
|
|
615
|
+
},
|
|
616
|
+
commonDeferOptions: {
|
|
617
|
+
// Defer options for deferred methods
|
|
618
|
+
delay: 300,
|
|
619
|
+
retry: 2,
|
|
620
|
+
},
|
|
621
|
+
errorPrefix: '[Products API] ', // Prefix for error messages
|
|
622
|
+
},
|
|
623
|
+
)
|
|
639
624
|
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
625
|
+
// Use the integrated methods
|
|
626
|
+
productsClient.get('/products/1').then(console.log, console.warn)
|
|
627
|
+
|
|
628
|
+
// Override per request
|
|
629
|
+
productsClient
|
|
630
|
+
.post(
|
|
631
|
+
'/products/add',
|
|
632
|
+
{ title: 'New Product' },
|
|
633
|
+
{
|
|
634
|
+
timeout: 10000, // Override timeout
|
|
635
|
+
},
|
|
636
|
+
)
|
|
637
|
+
.then(console.log, console.warn)
|
|
638
|
+
|
|
639
|
+
// Use deferred (debounced) methods
|
|
640
|
+
const deferredSearch = productsClient.get.deferred(
|
|
641
|
+
{ delay: 300 },
|
|
642
|
+
'/products/search',
|
|
649
643
|
)
|
|
644
|
+
deferredSearch({ q: 'iphone' }).then(console.log)
|
|
645
|
+
```
|
|
650
646
|
|
|
651
|
-
|
|
652
|
-
postClient(
|
|
653
|
-
'[DUMMYJSON-DOT-COM]/products/add',
|
|
654
|
-
{ title: 'New Product' }, // data/body
|
|
655
|
-
{}, // other options
|
|
656
|
-
).then(result => console.log('Product created:', result))
|
|
647
|
+
#### Example: Multiple clients with different configurations
|
|
657
648
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
)
|
|
673
|
-
|
|
674
|
-
|
|
649
|
+
```javascript
|
|
650
|
+
import { ApiClient } from '@superutils/fetch'
|
|
651
|
+
|
|
652
|
+
// Client for public API (no auth required)
|
|
653
|
+
const publicApi = new ApiClient('https://api.example.com/public')
|
|
654
|
+
|
|
655
|
+
// Client for authenticated endpoints
|
|
656
|
+
const privateApi = new ApiClient('https://api.example.com/private', {
|
|
657
|
+
fixedOptions: {
|
|
658
|
+
headers: { Authorization: 'Bearer token' },
|
|
659
|
+
},
|
|
660
|
+
commonOptions: {
|
|
661
|
+
timeout: 10000,
|
|
662
|
+
},
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
// Use them independently
|
|
666
|
+
publicApi.get('/posts').then(console.log)
|
|
667
|
+
privateApi.post('/user/profile', { name: 'John' }).then(console.log)
|
|
675
668
|
```
|
|
676
669
|
|
|
677
670
|
<div id="fetch-func"></div>
|
|
@@ -682,29 +675,29 @@ The `fetchFunc` option allows you to replace the default request engine. This en
|
|
|
682
675
|
|
|
683
676
|
```typescript
|
|
684
677
|
import fetch, {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
678
|
+
FetchCustomOptions,
|
|
679
|
+
FetchFunc,
|
|
680
|
+
FetchOptions,
|
|
688
681
|
} from '@superutils/fetch'
|
|
689
682
|
import axios from 'axios'
|
|
690
683
|
|
|
691
684
|
type Product = {
|
|
692
|
-
|
|
693
|
-
|
|
685
|
+
id: number
|
|
686
|
+
title: string
|
|
694
687
|
}
|
|
695
688
|
fetch
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
689
|
+
.get<{ data: Product }>('[DUMMYJSON-DOT-COM]/products/1', {
|
|
690
|
+
/**
|
|
691
|
+
* Note: Ensure request options are compatible with the third-party
|
|
692
|
+
* engine's configuration schema.
|
|
693
|
+
*
|
|
694
|
+
* Check {@link FetchOptions} (includes `FetchCustomOptions`).
|
|
695
|
+
*/
|
|
696
|
+
fetchFunc: axios as FetchFunc,
|
|
697
|
+
|
|
698
|
+
// if request fails retry maximus 3 more times
|
|
699
|
+
retry: 3,
|
|
700
|
+
// ...additional options
|
|
701
|
+
})
|
|
702
|
+
.then(({ data }) => console.log({ product: data }))
|
|
710
703
|
```
|