@superutils/fetch 1.2.2 → 1.3.0

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.
Files changed (4) hide show
  1. package/README.md +130 -61
  2. package/dist/index.d.ts +462 -245
  3. package/dist/index.js +256 -170
  4. package/package.json +5 -5
package/README.md CHANGED
@@ -67,39 +67,39 @@ All fetch calls return a `PromisE` (`@superutils/promise`) instance which means
67
67
 
68
68
  1. Status tracking: all instances come with `.pending`, `.resolved` and `.rejected` attributes that indicate the current state of the promise.
69
69
 
70
- ```javascript
71
- import fetch from '@superutils/fetch'
70
+ ```javascript
71
+ import fetch from '@superutils/fetch'
72
72
 
73
- const request = fetch('https://dummyjson.com/products/1')
73
+ const request = fetch('https://dummyjson.com/products/1')
74
74
 
75
- console.log(request.pending) // true
75
+ console.log(request.pending) // true
76
76
 
77
- request.then(() => {
78
- console.log(request.resolved) // true
79
- console.log(request.pending) // false
80
- console.log(request.rejected) // false
81
- })
82
- ```
77
+ request.then(() => {
78
+ console.log(request.resolved) // true
79
+ console.log(request.pending) // false
80
+ console.log(request.rejected) // false
81
+ })
82
+ ```
83
83
 
84
84
  2. Early finalization: all `PromisE` instances expose `.resolve()` and `.reject()` methods that allow early finalization and `.onEarlyFinalize` array that allows adding callbacks to be executed when the promise is finalized externally using these methods. Fetch promises utilize this to abort the request when appropriate.
85
85
 
86
- ```javascript
87
- import fetch from '@superutils/fetch'
86
+ ```javascript
87
+ import fetch from '@superutils/fetch'
88
88
 
89
- // Request that will take 5 seconds to resolve
90
- const request = fetch('https://dummyjson.com/products?delay=5000')
89
+ // Request that will take 5 seconds to resolve
90
+ const request = fetch('https://dummyjson.com/products?delay=5000')
91
91
 
92
- request.then(result => console.log(result), console.warn)
92
+ request.then(result => console.log(result), console.warn)
93
93
 
94
- // Add a callback to do stuff whenever request is aborted externally.
95
- // This will not be invoked if fetch fails or resolves (promise finalized naturally) using the Promise executor.
96
- request.onEarlyFinalize.push((resolved, valueOrReason) =>
97
- console.log('Aborted externally:', { resolved, valueOrReason }),
98
- )
94
+ // Add a callback to do stuff whenever request is aborted externally.
95
+ // This will not be invoked if fetch fails or resolves (promise finalized naturally) using the Promise executor.
96
+ request.onEarlyFinalize.push((resolved, valueOrReason) =>
97
+ console.log('Aborted externally:', { resolved, valueOrReason }),
98
+ )
99
99
 
100
- // resolve/reject before the promise is finalized
101
- request.reject(new Error('No longer needed'))
102
- ```
100
+ // resolve/reject before the promise is finalized
101
+ request.reject(new Error('No longer needed'))
102
+ ```
103
103
 
104
104
  <div id="methods"></div>
105
105
 
@@ -193,10 +193,11 @@ setTimeout(() => {
193
193
  3. `ResolveIgnored.NEVER`: The promise for the aborted "iphone" request is neither resolved nor rejected.
194
194
  It will remain pending indefinitely.
195
195
  - **`resolveError` (enum)**: Controls how failed requests are handled.
196
- 1. `ResolveError.NEVER`: Never resolve ignored promises. Caution: make sure this doesn't cause any memory leaks.
197
- 2. `ResolveError.WITH_LAST`: (default) resolve with active promise result, the one that caused the current promise/callback to be ignored.
198
- 3. `ResolveError.WITH_UNDEFINED`: resolve failed requests with `undefined` value
199
- 4. `ResolveError.WITH_ERROR`: The promise for the aborted "iphone" request is rejected with a `FetchError`.
196
+ 1. `ResolveError.NEVER`: The promise for a failed request will neither resolve nor reject, causing it to remain pending indefinitely.
197
+ > **Warning:** Use with caution, as this may lead to memory leaks if not handled properly.
198
+ 2. `ResolveError.WITH_ERROR`: The promise resolves with the `FetchError` object instead of being rejected.
199
+ 3. `ResolveError.WITH_UNDEFINED`: The promise resolves with an `undefined` value upon failure.
200
+ 4. `ResolveError.REJECT`: (Default) The promise is rejected with a `FetchError`, adhering to standard promise behavior.
200
201
 
201
202
  #### Using defaults to reduce redundancy
202
203
 
@@ -314,6 +315,7 @@ const requestNewToken = fetch.post.deferred(
314
315
  )
315
316
 
316
317
  // First authenticate user to get the initial refresh token and then request new referesh tokens
318
+ // First authenticate user to get the initial refresh token and then request new refresh tokens
317
319
  fetch
318
320
  .post<{ refreshToken: string }>(
319
321
  'https://dummyjson.com/auth/login',
@@ -360,24 +362,9 @@ The following interceptor callbacks allow intercepting and/or transforming at di
360
362
  - Value returned (transformed) by an interceptor will be carried over to the subsequent interceptor of the same type.
361
363
  - There are 2 category of interceptors:
362
364
  - Local: interceptors provided when making a request.
363
- - Global: intereptors 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.
364
-
365
- **Example: Add global request and error interceptors**
365
+ - 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.
366
366
 
367
- ```javascript
368
- import fetch from '@superutils/fetch'
369
-
370
- const { interceptors } = fetch.defaults
371
- interceptors.request.push((url, options) => {
372
- // a headers to all requests make by the application
373
- options.headers.append('x-auth', 'token')
374
- })
375
-
376
- interceptors.error.push((err, url, options) => {
377
- // log whenever a request fails
378
- console.log('Error interceptor', err)
379
- })
380
- ```
367
+ **Example: Interceptor usage**
381
368
 
382
369
  ```javascript
383
370
  import fetch, { FetchError } from '@superutils/fetch'
@@ -408,6 +395,7 @@ const interceptors = {
408
395
  console.log('request was successful', { url, options })
409
396
 
410
397
  // You can transform the response by returning different `Response` object or even make a completely new HTTP reuqest.
398
+ // You can transform the response by returning different `Response` object or even make a completely new HTTP request.
411
399
  // The subsequent response interceptors will receive the returned response
412
400
  return fetch('https://dummyjson.com/products/1') // promise will be resolved automatically
413
401
  },
@@ -429,6 +417,28 @@ fetch
429
417
  .then(product => console.log({ product }))
430
418
  ```
431
419
 
420
+ **Example: Add global request and error interceptors**
421
+
422
+ ```javascript
423
+ import fetch from '@superutils/fetch'
424
+
425
+ const { interceptors } = fetch.defaults
426
+
427
+ interceptors.request.push((url, options) => {
428
+ // a headers to all requests make by the application
429
+ // add headers to all requests made by the application
430
+ options.headers.append('x-auth', 'token')
431
+ })
432
+
433
+ interceptors.error.push((err, url, options) => {
434
+ // log whenever a request fails
435
+ console.log('Error interceptor', err)
436
+ })
437
+
438
+ // Each time a requst is made using @superutils/fetch, the above interceptors will be executed when appropriate
439
+ fetch('https://dummyjson.com/products/1').then(console.log, console.warn)
440
+ ```
441
+
432
442
  <div id="retry"></div>
433
443
 
434
444
  ### Retry
@@ -499,33 +509,92 @@ fetch.get('https://dummyjson.com/products/1', {
499
509
 
500
510
  <div id="reusable-clients"></div>
501
511
 
502
- ### `createClient(method, )`: Reusable Clients
512
+ ### `createClient(fixedOptions, commonOptions, commonDeferOptions)`: Reusable Clients
513
+
514
+ The `createClient` utility streamlines the creation of dedicated API clients by generating pre-configured fetch functions. These functions can be equipped with default options like headers, timeouts, or a specific HTTP method, which minimizes code repetition across your application. If a method is not specified during creation, the client will default to `GET`.
503
515
 
504
- The `createClient` utility allows you to generate pre-configured fetch functions with default options, such as headers, timeouts, or specific HTTP methods. This is ideal for creating dedicated API clients for specific services to avoid repetition.
516
+ The returned client also includes a `.deferred()` method, providing the same debounce, throttle, and sequential execution capabilities found in functions like `fetch.get.deferred()`.
505
517
 
506
518
  ```javascript
507
519
  import { createClient } from '@superutils/fetch'
508
520
 
509
- // Create a client with default headers and a 5-second timeout
510
- const apiClient = createClient(undefined, {
511
- headers: {
512
- Authorization: 'Bearer my-secret-token',
513
- 'Content-Type': 'application/json',
521
+ // Create a "GET" client with default headers and a 5-second timeout
522
+ const apiClient = createClient(
523
+ {
524
+ // fixed options cannot be overridden
525
+ method: 'get',
514
526
  },
515
- timeout: 5000,
516
- })
527
+ {
528
+ // default options can be overridden
529
+ headers: {
530
+ Authorization: 'Bearer my-secret-token',
531
+ 'Content-Type': 'application/json',
532
+ },
533
+ timeout: 5000,
534
+ },
535
+ {
536
+ // default defer options (can be overridden)
537
+ delayMs: 300,
538
+ retry: 2, // If request fails, retry up to two more times
539
+ },
540
+ )
517
541
 
518
542
  // Use it just like the standard fetch
519
- apiClient('https://dummyjson.com/products/1').then(console.log)
543
+ apiClient('https://dummyjson.com/products/1', {
544
+ // The 'method' property cannot be overridden as it is used in the fixed options when creating the client.
545
+ // In TypeScript, the compiler will not allow this property.
546
+ // In Javascript, it will simply be ignored.
547
+ // method: 'post',
548
+ timeout: 3000, // The 'timeout' property can be overridden
549
+ }).then(console.log, console.warn)
550
+
551
+ // create a deferred client using "apiClient"
552
+ const deferredClient = apiClient.deferred(
553
+ { retry: 0 }, // disable retrying by overriding the `retry` defer option
554
+ 'https://dummyjson.com/products/1',
555
+ { timeout: 3000 },
556
+ )
557
+ deferredClient({ timeout: 10000 }) // timeout is overridden by individual request
558
+ .then(console.log, console.warn)
559
+ ```
560
+
561
+ ### `createPostClient(mandatoryOptions, commonOptions, commonDeferOptions)`: Reusable Post-like Clients
562
+
563
+ While `createClient()` is versatile enough for any HTTP method, `createPostClient()` is specifically designed for methods that require a request body, such as `DELETE`, `PATCH`, `POST`, and `PUT`. If a method is not provided, it defaults to `POST`. The generated client accepts an additional second parameter (`data`) for the request payload.
520
564
 
521
- // Create a specialized POST client
522
- const createProduct = createClient(
523
- 'get', // if provided will force all requests to be of this
565
+ Similar to `createClient`, the returned function comes equipped with a `.deferred()` method, enabling debounced, throttled, or sequential execution.
566
+
567
+ ```javascript
568
+ import { createPostClient } from '@superutils/fetch'
569
+
570
+ // Create a POST client with 10-second as the default timeout
571
+ const postClient = createPostClient(
572
+ {
573
+ method: 'post',
574
+ headers: { 'content-type': 'application/json' },
575
+ },
524
576
  { timeout: 10000 },
525
577
  )
526
578
 
527
- // No need to specify method: 'post'
528
- createProduct('https://dummyjson.com/products/add', {
529
- body: JSON.stringify({ title: 'New Product' }),
530
- }).then(console.log)
579
+ // Invoking `postClient()` automatically applies the pre-configured options
580
+ postClient(
581
+ 'https://dummyjson.com/products/add',
582
+ { title: 'New Product' }, // data/body
583
+ {}, // other options
584
+ ).then(console.log)
585
+
586
+ // create a deferred client using "postClient"
587
+ const updateProduct = postClient.deferred(
588
+ {
589
+ delayMs: 300, // debounce duration
590
+ onResult: console.log, // prints only successful results
591
+ },
592
+ 'https://dummyjson.com/products/add',
593
+ {
594
+ method: 'patch',
595
+ timeout: 3000,
596
+ },
597
+ )
598
+ updateProduct({ title: 'New title 1' }) // ignored by debounce
599
+ updateProduct({ title: 'New title 2' }) // executed
531
600
  ```