soon-fetch 4.0.0-beta.1 → 4.0.0-beta.3

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 CHANGED
@@ -1,1417 +1,1417 @@
1
- [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
2
-
3
- <!-- omit in toc -->
4
-
5
- ### soon-fetch
6
-
7
- **A lightweight http request lib , alternative to axios with timeout, request reusing, race, response cache ...**
8
-
9
- > - 🌐 automatic parse restful api url parameters
10
- > - ⭐ rapid define a request api
11
- > - ⌛ timeout disconnect
12
- > - 📦 request reusing
13
- > - 🚀 request race
14
- > - 📝 response cache
15
- > - 🔤 automatic serialization of JSON
16
- > - 📏 .min size less than **6K**, smaller after zip
17
- > - 💡 smart type tips with Typescript
18
-
19
- - [Example](#example)
20
- - [Features](#features)
21
- - [Shortcut](#shortcut)
22
- - [Restful Url Params](#restful-url-params)
23
- - [Timeout](#timeout)
24
- - [Share pending request](#share-pending-request)
25
- - [Response cache](#response-cache)
26
- - [Request race](#request-race)
27
- - [Rapid Define APIs](#rapid-define-apis)
28
- - [API](#api)
29
-
30
- ### Example
31
-
32
- > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
33
- > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
34
-
35
- ```typescript
36
- import { createSoon, soonFetch } from "soon-fetch";
37
-
38
- // 使用 soonFetch 作为基础请求函数
39
- const request = async <T>(url: string, options?: SoonOptions) => {
40
- const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get";
41
- const response = await soonFetch<T>({
42
- url,
43
- options,
44
- baseURL: '/api',
45
- baseOptions: {
46
- timeout: 20 * 1000,
47
- headers: new Headers({
48
- Authorization: "Bearer " + localStorage.getItem("token"),
49
- }),
50
- share: isGet ? true : false,
51
- staleTime: isGet ? 2 * 1000 : 0,
52
- },
53
- });
54
- return response;
55
- };
56
-
57
- const soon = createSoon(request);
58
-
59
- /** GET */
60
- soon.get("/user?id=123");
61
- soon.get("/user", { query: { id: 123 } });
62
- soon.get("/user/:id", { params: { id: 123 } });
63
-
64
- /** POST */
65
- soon.post("/login", { body: { username: "admin", password: "123456" } });
66
-
67
- /**Define API */
68
- export const login = soon
69
- .POST("/user/login")
70
- .Body<{ username: string; password: string }>()
71
- .Ok<{ token: string }>();
72
- //the develop tools will have type tips for request and response
73
- login({ username: "admin", password: "123" }).then((res) => {
74
- localStorage.setItem("token", res.token);
75
- });
76
- ```
77
-
78
- ### Features
79
-
80
- ##### Shortcut
81
-
82
- ```typescript
83
- soon.get(url, options);
84
- soon.post(url, options);
85
- soon.put(url, options);
86
- soon.patch(url, options);
87
- soon.delete(url, options);
88
- soon.head(url, options);
89
- soon.options(url, options);
90
- ```
91
-
92
- ##### Restful Url Params
93
-
94
- url like /:key , will handle the key
95
-
96
- ```typescript
97
- soon.get("/api/user/:id", { params: { id: 1 } });
98
- // api/user/1
99
- soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
100
- //api/engineer/5
101
- ```
102
-
103
- ##### Timeout
104
-
105
- ```typescript
106
- //** the request level timeout, will override the instance level timeout */
107
- soon.get(url, { timeout: 1000 * 20 });
108
- ```
109
-
110
- ##### Share pending request
111
-
112
- If a request is made again before the first completes, will reuse the first request instead of making a new request.
113
-
114
- ```ts
115
- soon.get(url, { share: true });
116
- ```
117
-
118
- ##### Response cache
119
-
120
- A cached response will be returned if the request is made again within the specified time.
121
-
122
- ```ts
123
- soon.get(url, { staleTime: 1000 * 60 * 5 });
124
- ```
125
-
126
- ##### Request race
127
-
128
- ‌If a second request is made before the first completes, abort the first to avoid race conditions from out-of-order responses.
129
-
130
- ```tsx
131
- import { useEffect, useRef, useState } from "react";
132
-
133
- type User = { name: string; job: string };
134
- const api = soon.GET("/api/users").Query<{ page: number }>().Ok<User[]>();
135
- export default function App() {
136
- const refAbort = useRef([]);
137
- const [list, setList] = useState<User[]>([]);
138
- const [page, setPage] = useState(1);
139
- useEffect(() => {
140
- api({ page }, { aborts: refAbort.current })
141
- .then(setList)
142
- .catch(console.log);
143
- }, [page]);
144
- return (
145
- <div>
146
- <button onClick={() => setPage((pre) => pre + 1)}>next</button>
147
- <div>
148
- {list.map((item) => (
149
- <div key={item.name}>{item.name}</div>
150
- ))}
151
- </div>
152
- </div>
153
- );
154
- }
155
- ```
156
-
157
- ##### Rapid Define APIs
158
-
159
- ```typescript
160
- //can be GET POST PATCH PUT DELETE
161
- soon.GET(url:string).Query<Query>().Ok<Response>()
162
- soon.POST(url:string).Body<Body>().Ok<Response>()
163
- soon.GET(url:string).Options({ timeout: 5000 }).Ok<Response>()
164
- soon.POST(url:string).Body<Body>().Options({ timeout: 5000 }).Ok<Response>()
165
- //define an api
166
- export const getUserInfo = soon.GET("/user/:id").Ok();
167
- //then use in any where
168
- getUserInfo({ id: 2 }).then((res) => console.log(res));
169
- //define an api with options
170
- export const getUserInfoWithOptions = soon.GET("/user/:id").Options({ timeout: 5000 }).Ok();
171
- //then use in any where
172
- getUserInfoWithOptions({ id: 2 }).then((res) => console.log(res));
173
-
174
- //with typescript,
175
- export const login = soon
176
- .POST("/user/login")
177
- .Body<{ username: string; password: string }>()
178
- .Ok<{ token: string }>();
179
- //the develop tools will have type tips for request and response
180
- login({ username: "admin", password: "123" }).then((res) => {
181
- localStorage.setItem("token", res.token);
182
- });
183
- ```
184
-
185
- ### API
186
-
187
- #### SoonOptions
188
-
189
- ```ts
190
- // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
191
- // RequestInit is fetch's init options
192
- type SoonOptions = Omit<RequestInit, "body"> & {
193
- body?: RequestInit["body"] | object;
194
- query?:
195
- | Record<
196
- string,
197
- | string
198
- | number
199
- | boolean
200
- | null
201
- | undefined
202
- | (string | number | boolean | null | undefined)[]
203
- >
204
- | URLSearchParams;
205
- params?: Record<string, string | number>;
206
- timeout?: number;
207
- aborts?: AbortController[] | never[];
208
- share?: boolean;
209
- staleTime?: number;
210
- };
211
- ```
212
-
213
- #### createSoon
214
-
215
- Create a soon request instance.
216
-
217
- **Parameters:**
218
-
219
- - `request`: A function to handle the actual request, receives url and options, returns a Promise
220
-
221
- **Returns:** An object containing request method, API methods (GET, POST, PUT, DELETE, PATCH), and shortcut methods (get, post, put, delete, patch, head, options)
222
-
223
- **Example:**
224
-
225
- ```typescript
226
- const soon = createSoon(
227
- async <T>(url: string, options?: SoonOptions): Promise<T> => {
228
- const response = await fetch(url, options);
229
- return response.json();
230
- }
231
- );
232
-
233
- // Usage example
234
- const data = await soon.get<{ id: number; name: string }[]> ("/api/users");
235
-
236
- // Define API with options
237
- export const login = soon
238
- .POST("/user/login")
239
- .Body<{ username: string; password: string }>()
240
- .Ok<{ token: string }>();
241
-
242
- login({ username: "admin", password: "123" }).then((res) => {
243
- localStorage.setItem("token", res.token);
244
- });
245
- ```
246
-
247
- #### createShortApi
248
-
249
- Factory function to create API shortcut methods.
250
- Used to generate type-safe API calling methods, supporting path parameters, query parameters, and request body.
251
-
252
- **Parameters:**
253
-
254
- - `wrapper`: Wrapper function to handle actual request logic
255
-
256
- **Returns:** An object containing GET, POST, PUT, DELETE, PATCH and other methods, each method supports chain calling
257
-
258
- **Example:**
259
-
260
- ```typescript
261
- const API = createShortApi(
262
- async <T>(url: string, method: string, params: Record<string, string | number> | undefined, query: any, body: any, options: any): Promise<T> => {
263
- // Handle request logic
264
- const { url: _url } = parseUrl(url, { params, query });
265
- const response = await fetch(_url, { ...options, method, body });
266
- return response.json();
267
- }
268
- );
269
-
270
- // Usage example
271
- const getUser = API.GET("/api/users/:id").Ok<{ id: number; name: string }>();
272
- const userData = await getUser({ id: 1 });
273
- ```
274
-
275
- #### createShortMethods
276
-
277
- Factory function to create shortcut methods.
278
-
279
- **Parameters:**
280
-
281
- - `methods`: HTTP methods array
282
- - `wrapper`: Wrapper function that receives method name and returns a function to process requests
283
-
284
- **Returns:** Shortcut call object containing specified methods
285
-
286
- **Example:**
287
-
288
- ```typescript
289
- const methods = createShortMethods(["get", "post"] as const, (method) => {
290
- return async <T>(url: string, options?: SoonOptions): Promise<T> => {
291
- const response = await fetch(url, { ...options, method });
292
- return response.json();
293
- };
294
- });
295
- // Usage: methods.get<{ id: number; name: string }[]>('/api/users')
296
- ```
297
-
298
- #### parseUrlOptions
299
-
300
- Parse URL options.
301
-
302
- **Parameters:**
303
-
304
- - `urlOptions`: Object containing url, options, baseURL and baseOptions
305
-
306
- **Returns:** Tuple of processed url and options
307
-
308
- **Example:**
309
-
310
- ```typescript
311
- const [url, options] = parseUrlOptions({
312
- url: "/api/users/:id",
313
- options: { params: { id: "123" } },
314
- baseURL: "https://api.example.com",
315
- });
316
- // Returns: ['https://api.example.com/api/users/123', options]
317
- ```
318
-
319
- #### mergeHeaders
320
-
321
- Merge multiple Headers objects.
322
-
323
- **Parameters:**
324
-
325
- - `headersList`: List of Headers objects to merge
326
-
327
- **Returns:** Merged Headers object, later ones will overwrite earlier ones with the same name
328
-
329
- **Example:**
330
-
331
- ```typescript
332
- const headers1: HeadersInit = { "Content-Type": "application/json" };
333
- const headers2: HeadersInit = { Authorization: "Bearer token" };
334
- const mergedHeaders = mergeHeaders(headers1, headers2);
335
- ```
336
-
337
- #### mergeSignals
338
-
339
- Merge multiple AbortSignal signals.
340
-
341
- **Parameters:**
342
-
343
- - `signals`: Array of AbortSignals to merge
344
- - `timeout`: Optional timeout time (milliseconds)
345
-
346
- **Returns:** Merged AbortSignal, any signal termination will trigger termination
347
-
348
- **Example:**
349
-
350
- ```typescript
351
- const controller1 = new AbortController();
352
- const controller2 = new AbortController();
353
- const mergedSignal = mergeSignals(
354
- [controller1.signal, controller2.signal],
355
- 5000
356
- );
357
- ```
358
-
359
- #### mergeUrl
360
-
361
- Merge URL and its related parameters.
362
- Handle baseURL, path parameters and query parameters to generate complete URL.
363
-
364
- **Parameters:**
365
-
366
- - `url`: Original URL
367
- - `config`: Configuration object, including query parameters, path parameters and base URL
368
-
369
- **Returns:** Processed complete URL
370
-
371
- **Example:**
372
-
373
- ```typescript
374
- const { url } = parseUrl("/api/users/:id", {
375
- params: { id: "123" },
376
- query: { filter: "active" },
377
- baseURL: "https://api.example.com",
378
- });
379
- // Returns: 'https://api.example.com/api/users/123?filter=active'
380
- ```
381
-
382
- #### mergeOptions
383
-
384
- Merge multiple option objects.
385
- Merge request options, including special handling of headers and signals.
386
-
387
- **Parameters:**
388
-
389
- - `optionsList`: List of option objects to merge
390
-
391
- **Returns:** Merged option object
392
-
393
- **Example:**
394
-
395
- ```typescript
396
- const defaultOptions = {
397
- timeout: 5000,
398
- headers: { "Content-Type": "application/json" },
399
- };
400
- const requestOptions = {
401
- method: "POST",
402
- body: JSON.stringify({ name: "John" }),
403
- };
404
- const mergedOptions = mergeOptions(defaultOptions, requestOptions);
405
- ```
406
-
407
- #### isBodyJson
408
-
409
- Determine if the request body is a JSON object.
410
- Check if body is a plain object, not special types like FormData or Blob.
411
-
412
- **Parameters:**
413
-
414
- - `body`: Request body
415
-
416
- **Returns:** Returns true if it is a JSON object, otherwise false
417
-
418
- **Example:**
419
-
420
- ```typescript
421
- isBodyJson({ name: "John" }); // true
422
- isBodyJson(new FormData()); // false
423
- isBodyJson("string"); // false
424
- ```
425
-
426
- #### genRequestKey
427
-
428
- Generate a unique identification key for the request.
429
- Generate a unique key value based on the request's URL, method, headers, body, query parameters, etc., used for caching and request sharing.
430
-
431
- **Parameters:**
432
-
433
- - `req`: Request object containing url and options
434
-
435
- **Returns:** Unique identification string of the request
436
-
437
- **Example:**
438
-
439
- ```typescript
440
- const key = genRequestKey({
441
- url: "/api/users",
442
- options: { method: "GET", params: { id: 1 } },
443
- });
444
- ```
445
-
446
- #### raceAbort
447
-
448
- Race condition handling function.
449
- Used to handle request race conditions, terminating previous requests.
450
-
451
- **Parameters:**
452
-
453
- - `abortController`: Controller of the current request
454
- - `controllers`: Array of existing controllers
455
-
456
- **Example:**
457
-
458
- ```typescript
459
- const controller = new AbortController();
460
- const controllers: AbortController[] = [];
461
- // 注意:raceAbort 函数已不再直接导出,而是通过 createRequestStore 使用
462
- ```
463
-
464
- #### createRequestStore
465
-
466
- Create request store instance.
467
- Provides request caching, sharing, and race condition handling.
468
-
469
- **Parameters:**
470
-
471
- - `options`: Configuration options
472
- - `maxCacheSize`: Maximum cache size (default: 100000)
473
- - `checkInterval`: Cache check interval in milliseconds (default: 60000)
474
-
475
- **Returns:** Request store object with the following methods:
476
- - `entry(key)`: Get entry for specific request key
477
- - `dispose()`: Dispose the store and clear intervals
478
- - `get(key)`: Get request by key
479
- - `set(key, value)`: Set request by key
480
- - `remove(key)`: Remove request by key
481
- - `getAll()`: Get all requests
482
- - `removeAll()`: Remove all requests
483
- - `clearCache()`: Clear all cache
484
- - `clearExpiredCache()`: Clear expired cache
485
- - `abortAll()`: Abort all requests
486
-
487
- **Example:**
488
-
489
- ```typescript
490
- const store = createRequestStore({ maxCacheSize: 1000, checkInterval: 30000 });
491
- // Use store in requests
492
- ```
493
-
494
- #### deepSort
495
-
496
- Deep sort object keys.
497
- Recursively sort the keys of an object to generate a stable object serialization result.
498
-
499
- **Parameters:**
500
-
501
- - `obj`: Object to sort
502
- - `sortArr`: Whether to sort arrays (default: false)
503
-
504
- **Returns:** Object with sorted keys
505
-
506
- **Example:**
507
-
508
- ```typescript
509
- const obj = { b: 2, a: 1, c: { z: 3, y: 2 } };
510
- const sorted = deepSort(obj);
511
- // Returns: { a: 1, b: 2, c: { y: 2, z: 3 } }
512
- ```
513
-
514
- #### createSilentRefresh
515
-
516
- Create silent refresh instance.
517
- Used to handle silent refresh functionality when token expires.
518
-
519
- **Parameters:**
520
-
521
- - `refresh_token_fn`: Function to refresh token
522
-
523
- **Returns:** Function that accepts success and failure callbacks
524
-
525
- **Example:**
526
-
527
- ```typescript
528
- const silentRefresh = createSilentRefresh(async () => {
529
- // Refresh token logic
530
- await refreshToken();
531
- });
532
-
533
- // Usage example
534
- silentRefresh(
535
- () => console.log("Refresh successful"),
536
- () => console.log("Refresh failed")
537
- );
538
- ```
539
-
540
- #### soonFetch
541
-
542
- A lightweight fetch wrapper with caching, sharing, and race condition handling.
543
-
544
- **Parameters:**
545
-
546
- - `config`: Configuration object
547
- - `url`: Request URL
548
- - `options`: Request options (SoonOptions)
549
- - `baseURL`: Base URL
550
- - `baseOptions`: Base request options
551
- - `store`: Custom request store
552
- - `sortRequestKey`: Whether to sort request key
553
-
554
- **Returns:** Promise that resolves to the response
555
-
556
- **Example:**
557
-
558
- ```typescript
559
- const data = await soonFetch<User[]>({
560
- url: "/api/users",
561
- options: {
562
- method: "GET",
563
- query: { page: 1 },
564
- share: true,
565
- staleTime: 5000,
566
- },
567
- baseURL: "https://api.example.com",
568
- });
569
- ```
570
-
571
- #### parseWithBase
572
-
573
- Parse base URL configuration.
574
- Process baseURL, headers, body and other configuration items to generate final request configuration.
575
-
576
- **Parameters:**
577
-
578
- - `urlOptions`: Object containing url, options, baseURL and baseOptions
579
-
580
- **Returns:** Processed url, options, is_body_json and abortController
581
-
582
- **Example:**
583
-
584
- ```typescript
585
- const result = parseWithBase({
586
- url: "/api/users",
587
- options: { method: "GET" },
588
- baseURL: "https://api.example.com",
589
- });
590
- ```
591
-
592
- #### toFormData
593
-
594
- Convert object to FormData.
595
- Used to convert plain objects to FormData format, supports files and regular values.
596
-
597
- **Parameters:**
598
-
599
- - `body`: Object to convert
600
-
601
- **Returns:** Converted FormData object
602
-
603
- **Example:**
604
-
605
- ```typescript
606
- const formData = toFormData({
607
- name: "John",
608
- avatar: fileBlob,
609
- age: 30,
610
- });
611
- ```
612
-
613
- #### progressDownload
614
-
615
- Download with progress tracking.
616
- Used to download files with progress updates.
617
-
618
- **Parameters:**
619
-
620
- - `response`: Response object
621
- - `onProgress`: Progress callback function
622
-
623
- **Returns:** Promise that resolves to ArrayBuffer
624
-
625
- **Example:**
626
-
627
- ```typescript
628
- const response = await fetch("/api/download");
629
- const buffer = await progressDownload(response, (progress, downloaded, total) => {
630
- console.log(`Progress: ${progress}%, Downloaded: ${downloaded}/${total}`);
631
- });
632
- ```
633
-
634
- #### progressReadBody
635
-
636
- Read response body with progress tracking.
637
- Used to read response bodies with progress updates.
638
-
639
- **Parameters:**
640
-
641
- - `body`: ReadableStream<Uint8Array>
642
- - `onProgress`: Progress callback function
643
- - `total`: Total size in bytes (default: 0)
644
-
645
- **Returns:** Promise that resolves to ArrayBuffer
646
-
647
- **Example:**
648
-
649
- ```typescript
650
- const response = await fetch("/api/download");
651
- const buffer = await progressReadBody(response.body!, (progress, downloaded, total) => {
652
- console.log(`Progress: ${progress}%, Downloaded: ${downloaded}/${total}`);
653
- });
654
- ```
655
-
656
- #### parseOptions
657
-
658
- Parse and merge request options.
659
- Used to process request options, including baseURL, headers, and body.
660
-
661
- **Parameters:**
662
-
663
- - `urlOptions`: Object containing url, options, baseURL, and baseOptions
664
-
665
- **Returns:** Object containing parsed url, options, is_body_json, and abortController
666
-
667
- **Example:**
668
-
669
- ```typescript
670
- const parsed = parseOptions({
671
- url: "/api/users",
672
- options: { method: "GET", query: { page: 1 } },
673
- baseURL: "https://api.example.com",
674
- baseOptions: { timeout: 5000 },
675
- });
676
- ```
677
-
678
- #### requestWithStore
679
-
680
- Request wrapper with store support.
681
- Used to handle requests with caching, sharing, and race condition handling.
682
-
683
- **Parameters:**
684
-
685
- - `store`: Request store instance
686
- - `requestFn`: Request function
687
- - `requestKey`: Request key
688
- - `fetchAbort`: AbortController
689
- - `options`: Options object
690
-
691
- **Returns:** Promise that resolves to the response
692
-
693
- **Example:**
694
-
695
- ```typescript
696
- const store = createRequestStore();
697
- const data = await requestWithStore(store, () => fetch(url, options), requestKey, abortController, {
698
- share: true,
699
- staleTime: 5000,
700
- });
701
- ```
702
-
703
- [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
704
-
705
- <!-- omit in toc -->
706
-
707
- #### soon-fetch
708
-
709
- **极轻量的请求库,不到 6K**
710
-
711
- > - 🌐 自动解析 rest Url 的参数
712
- > - ⭐ 快捷定义请求 api
713
- > - ⌛ 超时断开
714
- > - 📦 请求复用
715
- > - 🚀 请求竞态
716
- > - 📝 响应缓存
717
- > - 🔤 自动处理 JSON
718
- > - 📏 不到 **6K** , zip 后会更小
719
- > - 💡 用 typescript 有智能类型提醒
720
-
721
- - [示例](#示例)
722
-
723
- - [特别功能](#特别功能)
724
-
725
- - [快捷方法](#快捷方法)
726
- - [Restful Url 参数自动处理](#restful-url-参数自动处理)
727
- - [共享未完成的请求](#共享未完成的请求)
728
- - [超时](#超时)
729
- - [响应缓存](#响应缓存)
730
- - [请求竞态](#请求竞态)
731
- - [快速定义 API](#快速定义-api)
732
-
733
- - [API](#api-1)
734
-
735
- ### 示例
736
-
737
- > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
738
- > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
739
-
740
- ```typescript
741
- import { createSoon, soonFetch } from "soon-fetch";
742
-
743
- // 使用 soonFetch 作为基础请求函数
744
- const request = async <T>(url: string, options?: SoonOptions) => {
745
- const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get";
746
- const response = await soonFetch<T>({
747
- url,
748
- options,
749
- baseURL: '/api',
750
- baseOptions: {
751
- timeout: 20 * 1000,
752
- headers: new Headers({
753
- Authorization: "Bearer " + localStorage.getItem("token"),
754
- }),
755
- share: isGet ? true : false,
756
- staleTime: isGet ? 2 * 1000 : 0,
757
- },
758
- });
759
- return response;
760
- };
761
-
762
- const soon = createSoon(request);
763
-
764
- /** GET */
765
- soon.get("/user?id=123");
766
- soon.get("/user", { query: { id: 123 } });
767
- soon.get("/user/:id", { params: { id: 123 } });
768
-
769
- /** POST */
770
- soon.post("/login", { body: { username: "admin", password: "123456" } });
771
-
772
- /**定义 API */
773
- export const login = soon
774
- .POST("/user/login")
775
- .Body<{ username: string; password: string }>()
776
- .Ok<{ token: string }>();
777
-
778
- //开发工具会有请求和响应的智能提醒
779
- login({ username: "admin", password: "123" }).then((res) => {
780
- localStorage.setItem("token", res.token);
781
- });
782
- ```
783
-
784
- ### 特别功能
785
-
786
- ##### 快捷方法
787
-
788
- ```typescript
789
- soon.get(url, options);
790
- soon.post(url, options);
791
- soon.put(url, options);
792
- soon.patch(url, options);
793
- soon.delete(url, options);
794
- soon.head(url, options);
795
- soon.options(url, options);
796
- ```
797
-
798
- ###### Restful Url 参数自动处理
799
-
800
- url 包含 /:key 会解析匹配 key
801
-
802
- ```typescript
803
- soon.get("/api/user/:id", { params: { id: 1 } });
804
- // api/user/1
805
- soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
806
- //api/engineer/5
807
- ```
808
-
809
- ##### 超时
810
-
811
- ```typescript
812
- //** 请求级超时, 会覆盖实例级超时 */
813
- soon.get(url, { timeout: 1000 * 20 });
814
- ```
815
-
816
- ##### 共享未完成的请求
817
-
818
- 如果在第一个请求完成之前再次发起相同的请求,则会复用第一个请求,而不是发起新的请求。
819
-
820
- ```ts
821
- soon.get(url, { share: true });
822
- ```
823
-
824
- ##### 响应缓存
825
-
826
- 如果在指定时间内再次发起相同的请求,则会返回缓存的响应。
827
-
828
- ```ts
829
- soon.get(url, { staleTime: 1000 * 60 * 5 });
830
- ```
831
-
832
- ##### 请求竞态
833
-
834
- 如果在第一个请求完成之前发起第二个请求,则会中止第一个请求,以避免因响应顺序错乱导致的问题。
835
-
836
- ```tsx
837
- import { useEffect, useRef, useState } from "react";
838
-
839
- type User = { name: string; job: string };
840
- const api = soon.GET("/api/users").Query<{ page: number }>().Ok<User[]>();
841
- export default function App() {
842
- const refAbort = useRef([]);
843
- const [list, setList] = useState<User[]>([]);
844
- const [page, setPage] = useState(1);
845
- useEffect(() => {
846
- api({ page }, { aborts: refAbort.current })
847
- .then(setList)
848
- .catch(console.log);
849
- }, [page]);
850
- return (
851
- <div>
852
- <button onClick={() => setPage((pre) => pre + 1)}>next</button>
853
- <div>
854
- {list.map((item) => (
855
- <div key={item.name}>{item.name}</div>
856
- ))}
857
- </div>
858
- </div>
859
- );
860
- }
861
- ```
862
-
863
- ##### 快速定义 API
864
-
865
- ```ts
866
- //可以是 GET POST PATCH PUT DELETE
867
- //GET 请求数据传递至query,其他方法请求数据传递至body
868
- soon.GET(url:string).Query<Query>().Ok<Response>()
869
- soon.POST(url:string).Body<Body>().Ok<Response>()
870
- soon.GET(url:string).Options({ timeout: 5000 }).Ok<Response>()
871
- soon.POST(url:string).Body<Body>().Options({ timeout: 5000 }).Ok<Response>()
872
-
873
- //定义一个api
874
- export const getUserInfo=soon.GET('/user/:id').Ok()
875
- //使用
876
- getUserInfo({id:2}).then(res=>console.log(res))
877
- //定义一个带选项的api
878
- export const getUserInfoWithOptions=soon.GET('/user/:id').Options({ timeout: 5000 }).Ok()
879
- //使用
880
- getUserInfoWithOptions({id:2}).then(res=>console.log(res))
881
-
882
- //用typescript,
883
- export const login=soon
884
- .POST('/user/login')
885
- .Body<{username:string,password:string}>()
886
- .Ok<{token:string}>()
887
- //开发工具会有请求和响应的智能提醒
888
- login({username:'admin',password:'123'}).then(res=>{
889
- localStorage.setItem('token', res.token);
890
- })
891
- ```
892
-
893
- ### API
894
-
895
- #### SoonOptions
896
-
897
- ```ts
898
- // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
899
- // RequestInit 为原生 fetch 的 init 选项
900
- type SoonOptions = Omit<RequestInit, "body"> & {
901
- body?: RequestInit["body"] | object;
902
- query?:
903
- | Record<
904
- string,
905
- | string
906
- | number
907
- | boolean
908
- | null
909
- | undefined
910
- | (string | number | boolean | null | undefined)[]
911
- >
912
- | URLSearchParams;
913
- params?: Record<string, string | number>;
914
- timeout?: number;
915
- aborts?: AbortController[] | never[];
916
- share?: boolean;
917
- staleTime?: number;
918
- };
919
- ```
920
-
921
- #### createSoon
922
-
923
- 创建一个 soon 请求实例。
924
-
925
- **参数:**
926
-
927
- - `request`: 用于处理实际请求的函数,接收 url 和 options,返回一个 Promise
928
-
929
- **返回:** 包含 request 方法、API 方法(GET、POST、PUT、DELETE、PATCH)和快捷方法(get、post、put、delete、patch、head、options)的对象
930
-
931
- **示例:**
932
-
933
- ```typescript
934
- const soon = createSoon(
935
- async (url, options) => {
936
- const response = await fetch(url, options);
937
- return response.json();
938
- }
939
- );
940
-
941
- // 使用示例
942
- const data = await soon.get("/api/users");
943
-
944
- // 定义带选项的 API
945
- export const login = soon
946
- .POST("/user/login")
947
- .Body<{ username: string; password: string }>()
948
- .Ok<{ token: string }>();
949
-
950
- login({ username: "admin", password: "123" }).then((res) => {
951
- localStorage.setItem("token", res.token);
952
- });
953
- ```
954
-
955
- #### createShortApi
956
-
957
- 创建 API 快捷方法的工厂函数。
958
- 用于生成类型安全的 API 调用方法,支持路径参数、查询参数和请求体。
959
-
960
- **参数:**
961
-
962
- - `wrapper`: 包装函数,用于处理实际的请求逻辑
963
-
964
- **返回:** 包含 GET、POST、PUT、DELETE、PATCH 等方法的对象,每个方法都支持链式调用
965
-
966
- **示例:**
967
-
968
- ```typescript
969
- const API = createShortApi(
970
- async (url, method, params, query, body, options) => {
971
- // 处理请求逻辑
972
- const _url = mergeUrl(url, { params, query });
973
- const response = await fetch(_url, { ...options, method, body });
974
- return response.json();
975
- }
976
- );
977
-
978
- // 使用示例
979
- const getUser = API.GET("/api/users/:id").Ok();
980
- const userData = await getUser({ id: 1 });
981
- ```
982
-
983
- #### createShortMethods
984
-
985
- 创建快捷方法的工厂函数。
986
-
987
- **参数:**
988
-
989
- - `methods`: HTTP 方法数组
990
- - `wrapper`: 包装函数,接收方法名,返回处理请求的函数
991
-
992
- **返回:** 包含指定方法的快捷调用对象
993
-
994
- **示例:**
995
-
996
- ```typescript
997
- const methods = createShortMethods(["get", "post"] as const, (method) => {
998
- return (url, options) => fetch(url, { ...options, method });
999
- });
1000
- // 使用: methods.get('/api/users')
1001
- ```
1002
-
1003
- #### parseUrlOptions
1004
-
1005
- 解析 URL 选项。
1006
-
1007
- **参数:**
1008
-
1009
- - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
1010
-
1011
- **返回:** 处理后的 url 和 options 元组
1012
-
1013
- **示例:**
1014
-
1015
- ```typescript
1016
- const [url, options] = parseUrlOptions({
1017
- url: "/api/users/:id",
1018
- options: { params: { id: "123" } },
1019
- baseURL: "https://api.example.com",
1020
- });
1021
- // 返回: ['https://api.example.com/api/users/123', options]
1022
- ```
1023
-
1024
- #### mergeHeaders
1025
-
1026
- 合并多个 Headers 对象。
1027
-
1028
- **参数:**
1029
-
1030
- - `headersList`: 要合并的 Headers 对象列表
1031
-
1032
- **返回:** 合并后的 Headers 对象,后面的会覆盖前面的同名 header
1033
-
1034
- **示例:**
1035
-
1036
- ```typescript
1037
- const headers1: HeadersInit = { "Content-Type": "application/json" };
1038
- const headers2: HeadersInit = { Authorization: "Bearer token" };
1039
- const mergedHeaders = mergeHeaders(headers1, headers2);
1040
- ```
1041
-
1042
- #### mergeSignals
1043
-
1044
- 合并多个 AbortSignal 信号。
1045
-
1046
- **参数:**
1047
-
1048
- - `signals`: 要合并的 AbortSignal 数组
1049
- - `timeout`: 可选的超时时间(毫秒)
1050
-
1051
- **返回:** 合并后的 AbortSignal,任意一个信号终止都会触发终止
1052
-
1053
- **示例:**
1054
-
1055
- ```typescript
1056
- const controller1 = new AbortController();
1057
- const controller2 = new AbortController();
1058
- const mergedSignal = mergeSignals(
1059
- [controller1.signal, controller2.signal],
1060
- 5000
1061
- );
1062
- ```
1063
-
1064
- #### mergeUrl
1065
-
1066
- 合并 URL 及其相关参数。
1067
- 处理 baseURL、路径参数和查询参数,生成完整 URL。
1068
-
1069
- **参数:**
1070
-
1071
- - `url`: 原始 URL
1072
- - `config`: 配置对象,包含查询参数、路径参数和基础 URL
1073
-
1074
- **返回:** 处理后的完整 URL
1075
-
1076
- **示例:**
1077
-
1078
- ```typescript
1079
- const url = mergeUrl("/api/users/:id", {
1080
- params: { id: "123" },
1081
- query: { filter: "active" },
1082
- baseURL: "https://api.example.com",
1083
- });
1084
- // 返回: 'https://api.example.com/api/users/123?filter=active'
1085
- ```
1086
-
1087
- #### mergeOptions
1088
-
1089
- 合并多个选项对象。
1090
- 合并请求选项,包括 headers 和 signal 等特殊处理。
1091
-
1092
- **参数:**
1093
-
1094
- - `optionsList`: 要合并的选项对象列表
1095
-
1096
- **返回:** 合并后的选项对象
1097
-
1098
- **示例:**
1099
-
1100
- ```typescript
1101
- const defaultOptions = {
1102
- timeout: 5000,
1103
- headers: { "Content-Type": "application/json" },
1104
- };
1105
- const requestOptions = {
1106
- method: "POST",
1107
- body: JSON.stringify({ name: "John" }),
1108
- };
1109
- const mergedOptions = mergeOptions(defaultOptions, requestOptions);
1110
- ```
1111
-
1112
- #### isBodyJson
1113
-
1114
- 判断请求体是否为 JSON 对象。
1115
- 检查 body 是否为普通对象,而不是 FormData、Blob 等特殊类型。
1116
-
1117
- **参数:**
1118
-
1119
- - `body`: 请求体
1120
-
1121
- **返回:** 如果是 JSON 对象返回 true,否则返回 false
1122
-
1123
- **示例:**
1124
-
1125
- ```typescript
1126
- isBodyJson({ name: "John" }); // true
1127
- isBodyJson(new FormData()); // false
1128
- isBodyJson("string"); // false
1129
- ```
1130
-
1131
- #### genRequestKey
1132
-
1133
- 生成请求的唯一标识键。
1134
- 根据请求的 URL、方法、headers、body、查询参数等生成唯一键值,用于缓存和请求共享。
1135
-
1136
- **参数:**
1137
-
1138
- - `req`: 包含 url 和 options 的请求对象
1139
-
1140
- **返回:** 请求的唯一标识字符串
1141
-
1142
- **示例:**
1143
-
1144
- ```typescript
1145
- const key = genRequestKey({
1146
- url: "/api/users",
1147
- options: { method: "GET", params: { id: 1 } },
1148
- });
1149
- ```
1150
-
1151
- #### raceAbort
1152
-
1153
- 竞态处理函数。
1154
- 用于处理请求竞态,终止之前的请求。
1155
-
1156
- **参数:**
1157
-
1158
- - `abortController`: 当前请求的控制器
1159
- - `controllers`: 已存在的控制器数组
1160
-
1161
- **示例:**
1162
-
1163
- ```typescript
1164
- const controller = new AbortController();
1165
- const controllers = [];
1166
- raceAbort(controller, controllers); // 终止之前的请求并添加当前控制器
1167
- ```
1168
-
1169
- #### createRequestStore
1170
-
1171
- 创建请求存储实例。
1172
- 提供请求缓存、共享和竞态条件处理。
1173
-
1174
- **参数:**
1175
-
1176
- - `options`: 配置选项
1177
- - `maxCacheSize`: 最大缓存大小(默认: 100000)
1178
- - `checkInterval`: 缓存检查间隔(毫秒,默认: 60000)
1179
-
1180
- **返回:** 请求存储对象,包含以下方法:
1181
- - `entry(key)`: 获取特定请求键的条目
1182
- - `dispose()`: 销毁存储并清除定时器
1183
- - `get(key)`: 根据键获取请求
1184
- - `set(key, value)`: 根据键设置请求
1185
- - `remove(key)`: 根据键移除请求
1186
- - `getAll()`: 获取所有请求
1187
- - `removeAll()`: 移除所有请求
1188
- - `clearCache()`: 清除所有缓存
1189
- - `clearExpiredCache()`: 清除过期缓存
1190
- - `abortAll()`: 中止所有请求
1191
-
1192
- **示例:**
1193
-
1194
- ```typescript
1195
- const store = createRequestStore({ maxCacheSize: 1000, checkInterval: 30000 });
1196
- // 在请求中使用 store
1197
- ```
1198
-
1199
- #### deepSort
1200
-
1201
- 深度排序对象键。
1202
- 递归地对对象的键进行排序,用于生成稳定的对象序列化结果。
1203
-
1204
- **参数:**
1205
-
1206
- - `obj`: 要排序的对象
1207
- - `sortArr`: 是否对数组进行排序(默认: false)
1208
-
1209
- **返回:** 键排序后的对象
1210
-
1211
- **示例:**
1212
-
1213
- ```typescript
1214
- const obj = { b: 2, a: 1, c: { z: 3, y: 2 } };
1215
- const sorted = deepSort(obj);
1216
- // 返回: { a: 1, b: 2, c: { y: 2, z: 3 } }
1217
- ```
1218
-
1219
- #### createSilentRefresh
1220
-
1221
- 创建静默刷新实例。
1222
- 用于处理 token 过期时的静默刷新功能。
1223
-
1224
- **参数:**
1225
-
1226
- - `refresh_token_fn`: 刷新 token 的函数
1227
-
1228
- **返回:** 接收成功和失败回调的函数
1229
-
1230
- **示例:**
1231
-
1232
- ```typescript
1233
- const silentRefresh = createSilentRefresh(async () => {
1234
- // 刷新token逻辑
1235
- await refreshToken();
1236
- });
1237
-
1238
- // 使用示例
1239
- silentRefresh(
1240
- () => console.log("刷新成功"),
1241
- () => console.log("刷新失败")
1242
- );
1243
- ```
1244
-
1245
- #### soonFetch
1246
-
1247
- 一个轻量级的 fetch 包装器,支持缓存、共享和竞态条件处理。
1248
-
1249
- **参数:**
1250
-
1251
- - `config`: 配置对象
1252
- - `url`: 请求 URL
1253
- - `options`: 请求选项 (SoonOptions)
1254
- - `baseURL`: 基础 URL
1255
- - `baseOptions`: 基础请求选项
1256
- - `store`: 自定义请求存储
1257
- - `sortRequestKey`: 是否对请求键进行排序
1258
-
1259
- **返回:** 解析为响应的 Promise
1260
-
1261
- **示例:**
1262
-
1263
- ```typescript
1264
- const data = await soonFetch<User[]>({
1265
- url: "/api/users",
1266
- options: {
1267
- method: "GET",
1268
- query: { page: 1 },
1269
- share: true,
1270
- staleTime: 5000,
1271
- },
1272
- baseURL: "https://api.example.com",
1273
- });
1274
- ```
1275
-
1276
- #### parseWithBase
1277
-
1278
- 解析基础 URL 配置。
1279
- 处理 baseURL、headers、body 等配置项,生成最终的请求配置。
1280
-
1281
- **参数:**
1282
-
1283
- - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
1284
-
1285
- **返回:** 处理后的 url、options、is_body_json 和 abortController
1286
-
1287
- **示例:**
1288
-
1289
- ```typescript
1290
- const result = parseWithBase({
1291
- url: "/api/users",
1292
- options: { method: "GET" },
1293
- baseURL: "https://api.example.com",
1294
- });
1295
- ```
1296
-
1297
- #### toFormData
1298
-
1299
- 将对象转换为 FormData。
1300
- 用于将普通对象转换为 FormData 格式,支持文件和普通值。
1301
-
1302
- **参数:**
1303
-
1304
- - `body`: 要转换的对象
1305
-
1306
- **返回:** 转换后的 FormData 对象
1307
-
1308
- **示例:**
1309
-
1310
- ```typescript
1311
- const formData = toFormData({
1312
- name: "John",
1313
- avatar: fileBlob,
1314
- age: 30,
1315
- });
1316
- ```
1317
-
1318
- #### progressDownload
1319
-
1320
- 带进度的下载。
1321
- 用于下载文件并跟踪进度。
1322
-
1323
- **参数:**
1324
-
1325
- - `response`: 响应对象
1326
- - `onProgress`: 进度回调函数
1327
-
1328
- **返回:** 解析为 ArrayBuffer 的 Promise
1329
-
1330
- **示例:**
1331
-
1332
- ```typescript
1333
- const response = await fetch("/api/download");
1334
- const buffer = await progressDownload(response, (progress, downloaded, total) => {
1335
- console.log(`进度: ${progress}%, 已下载: ${downloaded}/${total}`);
1336
- });
1337
- ```
1338
-
1339
- #### progressReadBody
1340
-
1341
- 带进度的读取响应体。
1342
- 用于读取响应体并跟踪进度。
1343
-
1344
- **参数:**
1345
-
1346
- - `body`: ReadableStream<Uint8Array>
1347
- - `onProgress`: 进度回调函数
1348
- - `total`: 总大小(字节,默认: 0)
1349
-
1350
- **返回:** 解析为 ArrayBuffer 的 Promise
1351
-
1352
- **示例:**
1353
-
1354
- ```typescript
1355
- const response = await fetch("/api/download");
1356
- const buffer = await progressReadBody(response.body, (progress, downloaded, total) => {
1357
- console.log(`进度: ${progress}%, 已下载: ${downloaded}/${total}`);
1358
- });
1359
- ```
1360
-
1361
- #### parseOptions
1362
-
1363
- 解析和合并请求选项。
1364
- 用于处理请求选项,包括 baseURL、headers 和 body。
1365
-
1366
- **参数:**
1367
-
1368
- - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
1369
-
1370
- **返回:** 包含解析后的 url、options、is_body_json 和 abortController 的对象
1371
-
1372
- **示例:**
1373
-
1374
- ```typescript
1375
- const parsed = parseOptions({
1376
- url: "/api/users",
1377
- options: { method: "GET", query: { page: 1 } },
1378
- baseURL: "https://api.example.com",
1379
- baseOptions: { timeout: 5000 },
1380
- });
1381
- ```
1382
-
1383
- #### requestWithStore
1384
-
1385
- 带存储支持的请求包装器。
1386
- 用于处理带有缓存、共享和竞态条件处理的请求。
1387
-
1388
- **参数:**
1389
-
1390
- - `store`: 请求存储实例
1391
- - `requestFn`: 请求函数
1392
- - `requestKey`: 请求键
1393
- - `fetchAbort`: AbortController
1394
- - `options`: 选项对象
1395
-
1396
- **返回:** 解析为响应的 Promise
1397
-
1398
- **示例:**
1399
-
1400
- ```typescript
1401
- const store = createRequestStore();
1402
- const data = await requestWithStore(store, () => fetch(url, options), requestKey, abortController, {
1403
- share: true,
1404
- staleTime: 5000,
1405
- });
1406
- ```
1407
-
1408
-
1409
- [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
1410
-
1411
- <!-- omit in toc -->
1412
-
1413
- ##### 安装 Installation
1414
-
1415
- ```bash
1416
- npm install soon-fetch
1417
- ```
1
+ [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
2
+
3
+ <!-- omit in toc -->
4
+
5
+ ### soon-fetch
6
+
7
+ **A lightweight http request lib , alternative to axios with timeout, request reusing, race, response cache ...**
8
+
9
+ > - 🌐 automatic parse restful api url parameters
10
+ > - ⭐ rapid define a request api
11
+ > - ⌛ timeout disconnect
12
+ > - 📦 request reusing
13
+ > - 🚀 request race
14
+ > - 📝 response cache
15
+ > - 🔤 automatic serialization of JSON
16
+ > - 📏 .min size less than **6K**, smaller after zip
17
+ > - 💡 smart type tips with Typescript
18
+
19
+ - [Example](#example)
20
+ - [Features](#features)
21
+ - [Shortcut](#shortcut)
22
+ - [Restful Url Params](#restful-url-params)
23
+ - [Timeout](#timeout)
24
+ - [Share pending request](#share-pending-request)
25
+ - [Response cache](#response-cache)
26
+ - [Request race](#request-race)
27
+ - [Rapid Define APIs](#rapid-define-apis)
28
+ - [API](#api)
29
+
30
+ ### Example
31
+
32
+ > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
33
+ > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
34
+
35
+ ```typescript
36
+ import { createSoon, soonFetch } from "soon-fetch";
37
+
38
+ // 使用 soonFetch 作为基础请求函数
39
+ const request = async <T>(url: string, options?: SoonOptions) => {
40
+ const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get";
41
+ const response = await soonFetch<T>({
42
+ url,
43
+ options,
44
+ baseURL: '/api',
45
+ baseOptions: {
46
+ timeout: 20 * 1000,
47
+ headers: new Headers({
48
+ Authorization: "Bearer " + localStorage.getItem("token"),
49
+ }),
50
+ share: isGet ? true : false,
51
+ staleTime: isGet ? 2 * 1000 : 0,
52
+ },
53
+ });
54
+ return response;
55
+ };
56
+
57
+ const soon = createSoon(request);
58
+
59
+ /** GET */
60
+ soon.get("/user?id=123");
61
+ soon.get("/user", { query: { id: 123 } });
62
+ soon.get("/user/:id", { params: { id: 123 } });
63
+
64
+ /** POST */
65
+ soon.post("/login", { body: { username: "admin", password: "123456" } });
66
+
67
+ /**Define API */
68
+ export const login = soon
69
+ .POST("/user/login")
70
+ .Body<{ username: string; password: string }>()
71
+ .Ok<{ token: string }>();
72
+ //the develop tools will have type tips for request and response
73
+ login({ username: "admin", password: "123" }).then((res) => {
74
+ localStorage.setItem("token", res.token);
75
+ });
76
+ ```
77
+
78
+ ### Features
79
+
80
+ ##### Shortcut
81
+
82
+ ```typescript
83
+ soon.get(url, options);
84
+ soon.post(url, options);
85
+ soon.put(url, options);
86
+ soon.patch(url, options);
87
+ soon.delete(url, options);
88
+ soon.head(url, options);
89
+ soon.options(url, options);
90
+ ```
91
+
92
+ ##### Restful Url Params
93
+
94
+ url like /:key , will handle the key
95
+
96
+ ```typescript
97
+ soon.get("/api/user/:id", { params: { id: 1 } });
98
+ // api/user/1
99
+ soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
100
+ //api/engineer/5
101
+ ```
102
+
103
+ ##### Timeout
104
+
105
+ ```typescript
106
+ //** the request level timeout, will override the instance level timeout */
107
+ soon.get(url, { timeout: 1000 * 20 });
108
+ ```
109
+
110
+ ##### Share pending request
111
+
112
+ If a request is made again before the first completes, will reuse the first request instead of making a new request.
113
+
114
+ ```ts
115
+ soon.get(url, { share: true });
116
+ ```
117
+
118
+ ##### Response cache
119
+
120
+ A cached response will be returned if the request is made again within the specified time.
121
+
122
+ ```ts
123
+ soon.get(url, { staleTime: 1000 * 60 * 5 });
124
+ ```
125
+
126
+ ##### Request race
127
+
128
+ ‌If a second request is made before the first completes, abort the first to avoid race conditions from out-of-order responses.
129
+
130
+ ```tsx
131
+ import { useEffect, useRef, useState } from "react";
132
+
133
+ type User = { name: string; job: string };
134
+ const api = soon.GET("/api/users").Query<{ page: number }>().Ok<User[]>();
135
+ export default function App() {
136
+ const refAbort = useRef([]);
137
+ const [list, setList] = useState<User[]>([]);
138
+ const [page, setPage] = useState(1);
139
+ useEffect(() => {
140
+ api({ page }, { aborts: refAbort.current })
141
+ .then(setList)
142
+ .catch(console.log);
143
+ }, [page]);
144
+ return (
145
+ <div>
146
+ <button onClick={() => setPage((pre) => pre + 1)}>next</button>
147
+ <div>
148
+ {list.map((item) => (
149
+ <div key={item.name}>{item.name}</div>
150
+ ))}
151
+ </div>
152
+ </div>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ##### Rapid Define APIs
158
+
159
+ ```typescript
160
+ //can be GET POST PATCH PUT DELETE
161
+ soon.GET(url:string).Query<Query>().Ok<Response>()
162
+ soon.POST(url:string).Body<Body>().Ok<Response>()
163
+ soon.GET(url:string).Options({ timeout: 5000 }).Ok<Response>()
164
+ soon.POST(url:string).Body<Body>().Options({ timeout: 5000 }).Ok<Response>()
165
+ //define an api
166
+ export const getUserInfo = soon.GET("/user/:id").Ok();
167
+ //then use in any where
168
+ getUserInfo({ id: 2 }).then((res) => console.log(res));
169
+ //define an api with options
170
+ export const getUserInfoWithOptions = soon.GET("/user/:id").Options({ timeout: 5000 }).Ok();
171
+ //then use in any where
172
+ getUserInfoWithOptions({ id: 2 }).then((res) => console.log(res));
173
+
174
+ //with typescript,
175
+ export const login = soon
176
+ .POST("/user/login")
177
+ .Body<{ username: string; password: string }>()
178
+ .Ok<{ token: string }>();
179
+ //the develop tools will have type tips for request and response
180
+ login({ username: "admin", password: "123" }).then((res) => {
181
+ localStorage.setItem("token", res.token);
182
+ });
183
+ ```
184
+
185
+ ### API
186
+
187
+ #### SoonOptions
188
+
189
+ ```ts
190
+ // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
191
+ // RequestInit is fetch's init options
192
+ type SoonOptions = Omit<RequestInit, "body"> & {
193
+ body?: RequestInit["body"] | object;
194
+ query?:
195
+ | Record<
196
+ string,
197
+ | string
198
+ | number
199
+ | boolean
200
+ | null
201
+ | undefined
202
+ | (string | number | boolean | null | undefined)[]
203
+ >
204
+ | URLSearchParams;
205
+ params?: Record<string, string | number>;
206
+ timeout?: number;
207
+ aborts?: AbortController[] | never[];
208
+ share?: boolean;
209
+ staleTime?: number;
210
+ };
211
+ ```
212
+
213
+ #### createSoon
214
+
215
+ Create a soon request instance.
216
+
217
+ **Parameters:**
218
+
219
+ - `request`: A function to handle the actual request, receives url and options, returns a Promise
220
+
221
+ **Returns:** An object containing request method, API methods (GET, POST, PUT, DELETE, PATCH), and shortcut methods (get, post, put, delete, patch, head, options)
222
+
223
+ **Example:**
224
+
225
+ ```typescript
226
+ const soon = createSoon(
227
+ async <T>(url: string, options?: SoonOptions): Promise<T> => {
228
+ const response = await fetch(url, options);
229
+ return response.json();
230
+ }
231
+ );
232
+
233
+ // Usage example
234
+ const data = await soon.get<{ id: number; name: string }[]> ("/api/users");
235
+
236
+ // Define API with options
237
+ export const login = soon
238
+ .POST("/user/login")
239
+ .Body<{ username: string; password: string }>()
240
+ .Ok<{ token: string }>();
241
+
242
+ login({ username: "admin", password: "123" }).then((res) => {
243
+ localStorage.setItem("token", res.token);
244
+ });
245
+ ```
246
+
247
+ #### createShortApi
248
+
249
+ Factory function to create API shortcut methods.
250
+ Used to generate type-safe API calling methods, supporting path parameters, query parameters, and request body.
251
+
252
+ **Parameters:**
253
+
254
+ - `wrapper`: Wrapper function to handle actual request logic
255
+
256
+ **Returns:** An object containing GET, POST, PUT, DELETE, PATCH and other methods, each method supports chain calling
257
+
258
+ **Example:**
259
+
260
+ ```typescript
261
+ const API = createShortApi(
262
+ async <T>(url: string, method: string, params: Record<string, string | number> | undefined, query: any, body: any, options: any): Promise<T> => {
263
+ // Handle request logic
264
+ const { url: _url } = parseUrl(url, { params, query });
265
+ const response = await fetch(_url, { ...options, method, body });
266
+ return response.json();
267
+ }
268
+ );
269
+
270
+ // Usage example
271
+ const getUser = API.GET("/api/users/:id").Ok<{ id: number; name: string }>();
272
+ const userData = await getUser({ id: 1 });
273
+ ```
274
+
275
+ #### createShortMethods
276
+
277
+ Factory function to create shortcut methods.
278
+
279
+ **Parameters:**
280
+
281
+ - `methods`: HTTP methods array
282
+ - `wrapper`: Wrapper function that receives method name and returns a function to process requests
283
+
284
+ **Returns:** Shortcut call object containing specified methods
285
+
286
+ **Example:**
287
+
288
+ ```typescript
289
+ const methods = createShortMethods(["get", "post"] as const, (method) => {
290
+ return async <T>(url: string, options?: SoonOptions): Promise<T> => {
291
+ const response = await fetch(url, { ...options, method });
292
+ return response.json();
293
+ };
294
+ });
295
+ // Usage: methods.get<{ id: number; name: string }[]>('/api/users')
296
+ ```
297
+
298
+ #### parseUrlOptions
299
+
300
+ Parse URL options.
301
+
302
+ **Parameters:**
303
+
304
+ - `urlOptions`: Object containing url, options, baseURL and baseOptions
305
+
306
+ **Returns:** Tuple of processed url and options
307
+
308
+ **Example:**
309
+
310
+ ```typescript
311
+ const [url, options] = parseUrlOptions({
312
+ url: "/api/users/:id",
313
+ options: { params: { id: "123" } },
314
+ baseURL: "https://api.example.com",
315
+ });
316
+ // Returns: ['https://api.example.com/api/users/123', options]
317
+ ```
318
+
319
+ #### mergeHeaders
320
+
321
+ Merge multiple Headers objects.
322
+
323
+ **Parameters:**
324
+
325
+ - `headersList`: List of Headers objects to merge
326
+
327
+ **Returns:** Merged Headers object, later ones will overwrite earlier ones with the same name
328
+
329
+ **Example:**
330
+
331
+ ```typescript
332
+ const headers1: HeadersInit = { "Content-Type": "application/json" };
333
+ const headers2: HeadersInit = { Authorization: "Bearer token" };
334
+ const mergedHeaders = mergeHeaders(headers1, headers2);
335
+ ```
336
+
337
+ #### mergeSignals
338
+
339
+ Merge multiple AbortSignal signals.
340
+
341
+ **Parameters:**
342
+
343
+ - `signals`: Array of AbortSignals to merge
344
+ - `timeout`: Optional timeout time (milliseconds)
345
+
346
+ **Returns:** Merged AbortSignal, any signal termination will trigger termination
347
+
348
+ **Example:**
349
+
350
+ ```typescript
351
+ const controller1 = new AbortController();
352
+ const controller2 = new AbortController();
353
+ const mergedSignal = mergeSignals(
354
+ [controller1.signal, controller2.signal],
355
+ 5000
356
+ );
357
+ ```
358
+
359
+ #### mergeUrl
360
+
361
+ Merge URL and its related parameters.
362
+ Handle baseURL, path parameters and query parameters to generate complete URL.
363
+
364
+ **Parameters:**
365
+
366
+ - `url`: Original URL
367
+ - `config`: Configuration object, including query parameters, path parameters and base URL
368
+
369
+ **Returns:** Processed complete URL
370
+
371
+ **Example:**
372
+
373
+ ```typescript
374
+ const { url } = parseUrl("/api/users/:id", {
375
+ params: { id: "123" },
376
+ query: { filter: "active" },
377
+ baseURL: "https://api.example.com",
378
+ });
379
+ // Returns: 'https://api.example.com/api/users/123?filter=active'
380
+ ```
381
+
382
+ #### mergeOptions
383
+
384
+ Merge multiple option objects.
385
+ Merge request options, including special handling of headers and signals.
386
+
387
+ **Parameters:**
388
+
389
+ - `optionsList`: List of option objects to merge
390
+
391
+ **Returns:** Merged option object
392
+
393
+ **Example:**
394
+
395
+ ```typescript
396
+ const defaultOptions = {
397
+ timeout: 5000,
398
+ headers: { "Content-Type": "application/json" },
399
+ };
400
+ const requestOptions = {
401
+ method: "POST",
402
+ body: JSON.stringify({ name: "John" }),
403
+ };
404
+ const mergedOptions = mergeOptions(defaultOptions, requestOptions);
405
+ ```
406
+
407
+ #### isBodyJson
408
+
409
+ Determine if the request body is a JSON object.
410
+ Check if body is a plain object, not special types like FormData or Blob.
411
+
412
+ **Parameters:**
413
+
414
+ - `body`: Request body
415
+
416
+ **Returns:** Returns true if it is a JSON object, otherwise false
417
+
418
+ **Example:**
419
+
420
+ ```typescript
421
+ isBodyJson({ name: "John" }); // true
422
+ isBodyJson(new FormData()); // false
423
+ isBodyJson("string"); // false
424
+ ```
425
+
426
+ #### genRequestKey
427
+
428
+ Generate a unique identification key for the request.
429
+ Generate a unique key value based on the request's URL, method, headers, body, query parameters, etc., used for caching and request sharing.
430
+
431
+ **Parameters:**
432
+
433
+ - `req`: Request object containing url and options
434
+
435
+ **Returns:** Unique identification string of the request
436
+
437
+ **Example:**
438
+
439
+ ```typescript
440
+ const key = genRequestKey({
441
+ url: "/api/users",
442
+ options: { method: "GET", params: { id: 1 } },
443
+ });
444
+ ```
445
+
446
+ #### raceAbort
447
+
448
+ Race condition handling function.
449
+ Used to handle request race conditions, terminating previous requests.
450
+
451
+ **Parameters:**
452
+
453
+ - `abortController`: Controller of the current request
454
+ - `controllers`: Array of existing controllers
455
+
456
+ **Example:**
457
+
458
+ ```typescript
459
+ const controller = new AbortController();
460
+ const controllers: AbortController[] = [];
461
+ // 注意:raceAbort 函数已不再直接导出,而是通过 createRequestStore 使用
462
+ ```
463
+
464
+ #### createRequestStore
465
+
466
+ Create request store instance.
467
+ Provides request caching, sharing, and race condition handling.
468
+
469
+ **Parameters:**
470
+
471
+ - `options`: Configuration options
472
+ - `maxCacheSize`: Maximum cache size (default: 100000)
473
+ - `checkInterval`: Cache check interval in milliseconds (default: 60000)
474
+
475
+ **Returns:** Request store object with the following methods:
476
+ - `entry(key)`: Get entry for specific request key
477
+ - `dispose()`: Dispose the store and clear intervals
478
+ - `get(key)`: Get request by key
479
+ - `set(key, value)`: Set request by key
480
+ - `remove(key)`: Remove request by key
481
+ - `getAll()`: Get all requests
482
+ - `removeAll()`: Remove all requests
483
+ - `clearCache()`: Clear all cache
484
+ - `clearExpiredCache()`: Clear expired cache
485
+ - `abortAll()`: Abort all requests
486
+
487
+ **Example:**
488
+
489
+ ```typescript
490
+ const store = createRequestStore({ maxCacheSize: 1000, checkInterval: 30000 });
491
+ // Use store in requests
492
+ ```
493
+
494
+ #### deepSort
495
+
496
+ Deep sort object keys.
497
+ Recursively sort the keys of an object to generate a stable object serialization result.
498
+
499
+ **Parameters:**
500
+
501
+ - `obj`: Object to sort
502
+ - `sortArr`: Whether to sort arrays (default: false)
503
+
504
+ **Returns:** Object with sorted keys
505
+
506
+ **Example:**
507
+
508
+ ```typescript
509
+ const obj = { b: 2, a: 1, c: { z: 3, y: 2 } };
510
+ const sorted = deepSort(obj);
511
+ // Returns: { a: 1, b: 2, c: { y: 2, z: 3 } }
512
+ ```
513
+
514
+ #### createSilentRefresh
515
+
516
+ Create silent refresh instance.
517
+ Used to handle silent refresh functionality when token expires.
518
+
519
+ **Parameters:**
520
+
521
+ - `refresh_token_fn`: Function to refresh token
522
+
523
+ **Returns:** Function that accepts success and failure callbacks
524
+
525
+ **Example:**
526
+
527
+ ```typescript
528
+ const silentRefresh = createSilentRefresh(async () => {
529
+ // Refresh token logic
530
+ await refreshToken();
531
+ });
532
+
533
+ // Usage example
534
+ silentRefresh(
535
+ () => console.log("Refresh successful"),
536
+ () => console.log("Refresh failed")
537
+ );
538
+ ```
539
+
540
+ #### soonFetch
541
+
542
+ A lightweight fetch wrapper with caching, sharing, and race condition handling.
543
+
544
+ **Parameters:**
545
+
546
+ - `config`: Configuration object
547
+ - `url`: Request URL
548
+ - `options`: Request options (SoonOptions)
549
+ - `baseURL`: Base URL
550
+ - `baseOptions`: Base request options
551
+ - `store`: Custom request store
552
+ - `sortRequestKey`: Whether to sort request key
553
+
554
+ **Returns:** Promise that resolves to the response
555
+
556
+ **Example:**
557
+
558
+ ```typescript
559
+ const data = await soonFetch<User[]>({
560
+ url: "/api/users",
561
+ options: {
562
+ method: "GET",
563
+ query: { page: 1 },
564
+ share: true,
565
+ staleTime: 5000,
566
+ },
567
+ baseURL: "https://api.example.com",
568
+ });
569
+ ```
570
+
571
+ #### parseWithBase
572
+
573
+ Parse base URL configuration.
574
+ Process baseURL, headers, body and other configuration items to generate final request configuration.
575
+
576
+ **Parameters:**
577
+
578
+ - `urlOptions`: Object containing url, options, baseURL and baseOptions
579
+
580
+ **Returns:** Processed url, options, is_body_json and abortController
581
+
582
+ **Example:**
583
+
584
+ ```typescript
585
+ const result = parseWithBase({
586
+ url: "/api/users",
587
+ options: { method: "GET" },
588
+ baseURL: "https://api.example.com",
589
+ });
590
+ ```
591
+
592
+ #### toFormData
593
+
594
+ Convert object to FormData.
595
+ Used to convert plain objects to FormData format, supports files and regular values.
596
+
597
+ **Parameters:**
598
+
599
+ - `body`: Object to convert
600
+
601
+ **Returns:** Converted FormData object
602
+
603
+ **Example:**
604
+
605
+ ```typescript
606
+ const formData = toFormData({
607
+ name: "John",
608
+ avatar: fileBlob,
609
+ age: 30,
610
+ });
611
+ ```
612
+
613
+ #### progressDownload
614
+
615
+ Download with progress tracking.
616
+ Used to download files with progress updates.
617
+
618
+ **Parameters:**
619
+
620
+ - `response`: Response object
621
+ - `onProgress`: Progress callback function
622
+
623
+ **Returns:** Promise that resolves to ArrayBuffer
624
+
625
+ **Example:**
626
+
627
+ ```typescript
628
+ const response = await fetch("/api/download");
629
+ const buffer = await progressDownload(response, (progress, downloaded, total) => {
630
+ console.log(`Progress: ${progress}%, Downloaded: ${downloaded}/${total}`);
631
+ });
632
+ ```
633
+
634
+ #### progressReadBody
635
+
636
+ Read response body with progress tracking.
637
+ Used to read response bodies with progress updates.
638
+
639
+ **Parameters:**
640
+
641
+ - `body`: ReadableStream<Uint8Array>
642
+ - `onProgress`: Progress callback function
643
+ - `total`: Total size in bytes (default: 0)
644
+
645
+ **Returns:** Promise that resolves to ArrayBuffer
646
+
647
+ **Example:**
648
+
649
+ ```typescript
650
+ const response = await fetch("/api/download");
651
+ const buffer = await progressReadBody(response.body!, (progress, downloaded, total) => {
652
+ console.log(`Progress: ${progress}%, Downloaded: ${downloaded}/${total}`);
653
+ });
654
+ ```
655
+
656
+ #### parseOptions
657
+
658
+ Parse and merge request options.
659
+ Used to process request options, including baseURL, headers, and body.
660
+
661
+ **Parameters:**
662
+
663
+ - `urlOptions`: Object containing url, options, baseURL, and baseOptions
664
+
665
+ **Returns:** Object containing parsed url, options, is_body_json, and abortController
666
+
667
+ **Example:**
668
+
669
+ ```typescript
670
+ const parsed = parseOptions({
671
+ url: "/api/users",
672
+ options: { method: "GET", query: { page: 1 } },
673
+ baseURL: "https://api.example.com",
674
+ baseOptions: { timeout: 5000 },
675
+ });
676
+ ```
677
+
678
+ #### requestWithStore
679
+
680
+ Request wrapper with store support.
681
+ Used to handle requests with caching, sharing, and race condition handling.
682
+
683
+ **Parameters:**
684
+
685
+ - `store`: Request store instance
686
+ - `requestFn`: Request function
687
+ - `requestKey`: Request key
688
+ - `fetchAbort`: AbortController
689
+ - `options`: Options object
690
+
691
+ **Returns:** Promise that resolves to the response
692
+
693
+ **Example:**
694
+
695
+ ```typescript
696
+ const store = createRequestStore();
697
+ const data = await requestWithStore(store, () => fetch(url, options), requestKey, abortController, {
698
+ share: true,
699
+ staleTime: 5000,
700
+ });
701
+ ```
702
+
703
+ [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
704
+
705
+ <!-- omit in toc -->
706
+
707
+ #### soon-fetch
708
+
709
+ **极轻量的请求库,不到 6K**
710
+
711
+ > - 🌐 自动解析 rest Url 的参数
712
+ > - ⭐ 快捷定义请求 api
713
+ > - ⌛ 超时断开
714
+ > - 📦 请求复用
715
+ > - 🚀 请求竞态
716
+ > - 📝 响应缓存
717
+ > - 🔤 自动处理 JSON
718
+ > - 📏 不到 **6K** , zip 后会更小
719
+ > - 💡 用 typescript 有智能类型提醒
720
+
721
+ - [示例](#示例)
722
+
723
+ - [特别功能](#特别功能)
724
+
725
+ - [快捷方法](#快捷方法)
726
+ - [Restful Url 参数自动处理](#restful-url-参数自动处理)
727
+ - [共享未完成的请求](#共享未完成的请求)
728
+ - [超时](#超时)
729
+ - [响应缓存](#响应缓存)
730
+ - [请求竞态](#请求竞态)
731
+ - [快速定义 API](#快速定义-api)
732
+
733
+ - [API](#api-1)
734
+
735
+ ### 示例
736
+
737
+ > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
738
+ > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
739
+
740
+ ```typescript
741
+ import { createSoon, soonFetch } from "soon-fetch";
742
+
743
+ // 使用 soonFetch 作为基础请求函数
744
+ const request = async <T>(url: string, options?: SoonOptions) => {
745
+ const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get";
746
+ const response = await soonFetch<T>({
747
+ url,
748
+ options,
749
+ baseURL: '/api',
750
+ baseOptions: {
751
+ timeout: 20 * 1000,
752
+ headers: new Headers({
753
+ Authorization: "Bearer " + localStorage.getItem("token"),
754
+ }),
755
+ share: isGet ? true : false,
756
+ staleTime: isGet ? 2 * 1000 : 0,
757
+ },
758
+ });
759
+ return response;
760
+ };
761
+
762
+ const soon = createSoon(request);
763
+
764
+ /** GET */
765
+ soon.get("/user?id=123");
766
+ soon.get("/user", { query: { id: 123 } });
767
+ soon.get("/user/:id", { params: { id: 123 } });
768
+
769
+ /** POST */
770
+ soon.post("/login", { body: { username: "admin", password: "123456" } });
771
+
772
+ /**定义 API */
773
+ export const login = soon
774
+ .POST("/user/login")
775
+ .Body<{ username: string; password: string }>()
776
+ .Ok<{ token: string }>();
777
+
778
+ //开发工具会有请求和响应的智能提醒
779
+ login({ username: "admin", password: "123" }).then((res) => {
780
+ localStorage.setItem("token", res.token);
781
+ });
782
+ ```
783
+
784
+ ### 特别功能
785
+
786
+ ##### 快捷方法
787
+
788
+ ```typescript
789
+ soon.get(url, options);
790
+ soon.post(url, options);
791
+ soon.put(url, options);
792
+ soon.patch(url, options);
793
+ soon.delete(url, options);
794
+ soon.head(url, options);
795
+ soon.options(url, options);
796
+ ```
797
+
798
+ ###### Restful Url 参数自动处理
799
+
800
+ url 包含 /:key 会解析匹配 key
801
+
802
+ ```typescript
803
+ soon.get("/api/user/:id", { params: { id: 1 } });
804
+ // api/user/1
805
+ soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
806
+ //api/engineer/5
807
+ ```
808
+
809
+ ##### 超时
810
+
811
+ ```typescript
812
+ //** 请求级超时, 会覆盖实例级超时 */
813
+ soon.get(url, { timeout: 1000 * 20 });
814
+ ```
815
+
816
+ ##### 共享未完成的请求
817
+
818
+ 如果在第一个请求完成之前再次发起相同的请求,则会复用第一个请求,而不是发起新的请求。
819
+
820
+ ```ts
821
+ soon.get(url, { share: true });
822
+ ```
823
+
824
+ ##### 响应缓存
825
+
826
+ 如果在指定时间内再次发起相同的请求,则会返回缓存的响应。
827
+
828
+ ```ts
829
+ soon.get(url, { staleTime: 1000 * 60 * 5 });
830
+ ```
831
+
832
+ ##### 请求竞态
833
+
834
+ 如果在第一个请求完成之前发起第二个请求,则会中止第一个请求,以避免因响应顺序错乱导致的问题。
835
+
836
+ ```tsx
837
+ import { useEffect, useRef, useState } from "react";
838
+
839
+ type User = { name: string; job: string };
840
+ const api = soon.GET("/api/users").Query<{ page: number }>().Ok<User[]>();
841
+ export default function App() {
842
+ const refAbort = useRef([]);
843
+ const [list, setList] = useState<User[]>([]);
844
+ const [page, setPage] = useState(1);
845
+ useEffect(() => {
846
+ api({ page }, { aborts: refAbort.current })
847
+ .then(setList)
848
+ .catch(console.log);
849
+ }, [page]);
850
+ return (
851
+ <div>
852
+ <button onClick={() => setPage((pre) => pre + 1)}>next</button>
853
+ <div>
854
+ {list.map((item) => (
855
+ <div key={item.name}>{item.name}</div>
856
+ ))}
857
+ </div>
858
+ </div>
859
+ );
860
+ }
861
+ ```
862
+
863
+ ##### 快速定义 API
864
+
865
+ ```ts
866
+ //可以是 GET POST PATCH PUT DELETE
867
+ //GET 请求数据传递至query,其他方法请求数据传递至body
868
+ soon.GET(url:string).Query<Query>().Ok<Response>()
869
+ soon.POST(url:string).Body<Body>().Ok<Response>()
870
+ soon.GET(url:string).Options({ timeout: 5000 }).Ok<Response>()
871
+ soon.POST(url:string).Body<Body>().Options({ timeout: 5000 }).Ok<Response>()
872
+
873
+ //定义一个api
874
+ export const getUserInfo=soon.GET('/user/:id').Ok()
875
+ //使用
876
+ getUserInfo({id:2}).then(res=>console.log(res))
877
+ //定义一个带选项的api
878
+ export const getUserInfoWithOptions=soon.GET('/user/:id').Options({ timeout: 5000 }).Ok()
879
+ //使用
880
+ getUserInfoWithOptions({id:2}).then(res=>console.log(res))
881
+
882
+ //用typescript,
883
+ export const login=soon
884
+ .POST('/user/login')
885
+ .Body<{username:string,password:string}>()
886
+ .Ok<{token:string}>()
887
+ //开发工具会有请求和响应的智能提醒
888
+ login({username:'admin',password:'123'}).then(res=>{
889
+ localStorage.setItem('token', res.token);
890
+ })
891
+ ```
892
+
893
+ ### API
894
+
895
+ #### SoonOptions
896
+
897
+ ```ts
898
+ // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
899
+ // RequestInit 为原生 fetch 的 init 选项
900
+ type SoonOptions = Omit<RequestInit, "body"> & {
901
+ body?: RequestInit["body"] | object;
902
+ query?:
903
+ | Record<
904
+ string,
905
+ | string
906
+ | number
907
+ | boolean
908
+ | null
909
+ | undefined
910
+ | (string | number | boolean | null | undefined)[]
911
+ >
912
+ | URLSearchParams;
913
+ params?: Record<string, string | number>;
914
+ timeout?: number;
915
+ aborts?: AbortController[] | never[];
916
+ share?: boolean;
917
+ staleTime?: number;
918
+ };
919
+ ```
920
+
921
+ #### createSoon
922
+
923
+ 创建一个 soon 请求实例。
924
+
925
+ **参数:**
926
+
927
+ - `request`: 用于处理实际请求的函数,接收 url 和 options,返回一个 Promise
928
+
929
+ **返回:** 包含 request 方法、API 方法(GET、POST、PUT、DELETE、PATCH)和快捷方法(get、post、put、delete、patch、head、options)的对象
930
+
931
+ **示例:**
932
+
933
+ ```typescript
934
+ const soon = createSoon(
935
+ async (url, options) => {
936
+ const response = await fetch(url, options);
937
+ return response.json();
938
+ }
939
+ );
940
+
941
+ // 使用示例
942
+ const data = await soon.get("/api/users");
943
+
944
+ // 定义带选项的 API
945
+ export const login = soon
946
+ .POST("/user/login")
947
+ .Body<{ username: string; password: string }>()
948
+ .Ok<{ token: string }>();
949
+
950
+ login({ username: "admin", password: "123" }).then((res) => {
951
+ localStorage.setItem("token", res.token);
952
+ });
953
+ ```
954
+
955
+ #### createShortApi
956
+
957
+ 创建 API 快捷方法的工厂函数。
958
+ 用于生成类型安全的 API 调用方法,支持路径参数、查询参数和请求体。
959
+
960
+ **参数:**
961
+
962
+ - `wrapper`: 包装函数,用于处理实际的请求逻辑
963
+
964
+ **返回:** 包含 GET、POST、PUT、DELETE、PATCH 等方法的对象,每个方法都支持链式调用
965
+
966
+ **示例:**
967
+
968
+ ```typescript
969
+ const API = createShortApi(
970
+ async (url, method, params, query, body, options) => {
971
+ // 处理请求逻辑
972
+ const _url = mergeUrl(url, { params, query });
973
+ const response = await fetch(_url, { ...options, method, body });
974
+ return response.json();
975
+ }
976
+ );
977
+
978
+ // 使用示例
979
+ const getUser = API.GET("/api/users/:id").Ok();
980
+ const userData = await getUser({ id: 1 });
981
+ ```
982
+
983
+ #### createShortMethods
984
+
985
+ 创建快捷方法的工厂函数。
986
+
987
+ **参数:**
988
+
989
+ - `methods`: HTTP 方法数组
990
+ - `wrapper`: 包装函数,接收方法名,返回处理请求的函数
991
+
992
+ **返回:** 包含指定方法的快捷调用对象
993
+
994
+ **示例:**
995
+
996
+ ```typescript
997
+ const methods = createShortMethods(["get", "post"] as const, (method) => {
998
+ return (url, options) => fetch(url, { ...options, method });
999
+ });
1000
+ // 使用: methods.get('/api/users')
1001
+ ```
1002
+
1003
+ #### parseUrlOptions
1004
+
1005
+ 解析 URL 选项。
1006
+
1007
+ **参数:**
1008
+
1009
+ - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
1010
+
1011
+ **返回:** 处理后的 url 和 options 元组
1012
+
1013
+ **示例:**
1014
+
1015
+ ```typescript
1016
+ const [url, options] = parseUrlOptions({
1017
+ url: "/api/users/:id",
1018
+ options: { params: { id: "123" } },
1019
+ baseURL: "https://api.example.com",
1020
+ });
1021
+ // 返回: ['https://api.example.com/api/users/123', options]
1022
+ ```
1023
+
1024
+ #### mergeHeaders
1025
+
1026
+ 合并多个 Headers 对象。
1027
+
1028
+ **参数:**
1029
+
1030
+ - `headersList`: 要合并的 Headers 对象列表
1031
+
1032
+ **返回:** 合并后的 Headers 对象,后面的会覆盖前面的同名 header
1033
+
1034
+ **示例:**
1035
+
1036
+ ```typescript
1037
+ const headers1: HeadersInit = { "Content-Type": "application/json" };
1038
+ const headers2: HeadersInit = { Authorization: "Bearer token" };
1039
+ const mergedHeaders = mergeHeaders(headers1, headers2);
1040
+ ```
1041
+
1042
+ #### mergeSignals
1043
+
1044
+ 合并多个 AbortSignal 信号。
1045
+
1046
+ **参数:**
1047
+
1048
+ - `signals`: 要合并的 AbortSignal 数组
1049
+ - `timeout`: 可选的超时时间(毫秒)
1050
+
1051
+ **返回:** 合并后的 AbortSignal,任意一个信号终止都会触发终止
1052
+
1053
+ **示例:**
1054
+
1055
+ ```typescript
1056
+ const controller1 = new AbortController();
1057
+ const controller2 = new AbortController();
1058
+ const mergedSignal = mergeSignals(
1059
+ [controller1.signal, controller2.signal],
1060
+ 5000
1061
+ );
1062
+ ```
1063
+
1064
+ #### mergeUrl
1065
+
1066
+ 合并 URL 及其相关参数。
1067
+ 处理 baseURL、路径参数和查询参数,生成完整 URL。
1068
+
1069
+ **参数:**
1070
+
1071
+ - `url`: 原始 URL
1072
+ - `config`: 配置对象,包含查询参数、路径参数和基础 URL
1073
+
1074
+ **返回:** 处理后的完整 URL
1075
+
1076
+ **示例:**
1077
+
1078
+ ```typescript
1079
+ const url = mergeUrl("/api/users/:id", {
1080
+ params: { id: "123" },
1081
+ query: { filter: "active" },
1082
+ baseURL: "https://api.example.com",
1083
+ });
1084
+ // 返回: 'https://api.example.com/api/users/123?filter=active'
1085
+ ```
1086
+
1087
+ #### mergeOptions
1088
+
1089
+ 合并多个选项对象。
1090
+ 合并请求选项,包括 headers 和 signal 等特殊处理。
1091
+
1092
+ **参数:**
1093
+
1094
+ - `optionsList`: 要合并的选项对象列表
1095
+
1096
+ **返回:** 合并后的选项对象
1097
+
1098
+ **示例:**
1099
+
1100
+ ```typescript
1101
+ const defaultOptions = {
1102
+ timeout: 5000,
1103
+ headers: { "Content-Type": "application/json" },
1104
+ };
1105
+ const requestOptions = {
1106
+ method: "POST",
1107
+ body: JSON.stringify({ name: "John" }),
1108
+ };
1109
+ const mergedOptions = mergeOptions(defaultOptions, requestOptions);
1110
+ ```
1111
+
1112
+ #### isBodyJson
1113
+
1114
+ 判断请求体是否为 JSON 对象。
1115
+ 检查 body 是否为普通对象,而不是 FormData、Blob 等特殊类型。
1116
+
1117
+ **参数:**
1118
+
1119
+ - `body`: 请求体
1120
+
1121
+ **返回:** 如果是 JSON 对象返回 true,否则返回 false
1122
+
1123
+ **示例:**
1124
+
1125
+ ```typescript
1126
+ isBodyJson({ name: "John" }); // true
1127
+ isBodyJson(new FormData()); // false
1128
+ isBodyJson("string"); // false
1129
+ ```
1130
+
1131
+ #### genRequestKey
1132
+
1133
+ 生成请求的唯一标识键。
1134
+ 根据请求的 URL、方法、headers、body、查询参数等生成唯一键值,用于缓存和请求共享。
1135
+
1136
+ **参数:**
1137
+
1138
+ - `req`: 包含 url 和 options 的请求对象
1139
+
1140
+ **返回:** 请求的唯一标识字符串
1141
+
1142
+ **示例:**
1143
+
1144
+ ```typescript
1145
+ const key = genRequestKey({
1146
+ url: "/api/users",
1147
+ options: { method: "GET", params: { id: 1 } },
1148
+ });
1149
+ ```
1150
+
1151
+ #### raceAbort
1152
+
1153
+ 竞态处理函数。
1154
+ 用于处理请求竞态,终止之前的请求。
1155
+
1156
+ **参数:**
1157
+
1158
+ - `abortController`: 当前请求的控制器
1159
+ - `controllers`: 已存在的控制器数组
1160
+
1161
+ **示例:**
1162
+
1163
+ ```typescript
1164
+ const controller = new AbortController();
1165
+ const controllers = [];
1166
+ raceAbort(controller, controllers); // 终止之前的请求并添加当前控制器
1167
+ ```
1168
+
1169
+ #### createRequestStore
1170
+
1171
+ 创建请求存储实例。
1172
+ 提供请求缓存、共享和竞态条件处理。
1173
+
1174
+ **参数:**
1175
+
1176
+ - `options`: 配置选项
1177
+ - `maxCacheSize`: 最大缓存大小(默认: 100000)
1178
+ - `checkInterval`: 缓存检查间隔(毫秒,默认: 60000)
1179
+
1180
+ **返回:** 请求存储对象,包含以下方法:
1181
+ - `entry(key)`: 获取特定请求键的条目
1182
+ - `dispose()`: 销毁存储并清除定时器
1183
+ - `get(key)`: 根据键获取请求
1184
+ - `set(key, value)`: 根据键设置请求
1185
+ - `remove(key)`: 根据键移除请求
1186
+ - `getAll()`: 获取所有请求
1187
+ - `removeAll()`: 移除所有请求
1188
+ - `clearCache()`: 清除所有缓存
1189
+ - `clearExpiredCache()`: 清除过期缓存
1190
+ - `abortAll()`: 中止所有请求
1191
+
1192
+ **示例:**
1193
+
1194
+ ```typescript
1195
+ const store = createRequestStore({ maxCacheSize: 1000, checkInterval: 30000 });
1196
+ // 在请求中使用 store
1197
+ ```
1198
+
1199
+ #### deepSort
1200
+
1201
+ 深度排序对象键。
1202
+ 递归地对对象的键进行排序,用于生成稳定的对象序列化结果。
1203
+
1204
+ **参数:**
1205
+
1206
+ - `obj`: 要排序的对象
1207
+ - `sortArr`: 是否对数组进行排序(默认: false)
1208
+
1209
+ **返回:** 键排序后的对象
1210
+
1211
+ **示例:**
1212
+
1213
+ ```typescript
1214
+ const obj = { b: 2, a: 1, c: { z: 3, y: 2 } };
1215
+ const sorted = deepSort(obj);
1216
+ // 返回: { a: 1, b: 2, c: { y: 2, z: 3 } }
1217
+ ```
1218
+
1219
+ #### createSilentRefresh
1220
+
1221
+ 创建静默刷新实例。
1222
+ 用于处理 token 过期时的静默刷新功能。
1223
+
1224
+ **参数:**
1225
+
1226
+ - `refresh_token_fn`: 刷新 token 的函数
1227
+
1228
+ **返回:** 接收成功和失败回调的函数
1229
+
1230
+ **示例:**
1231
+
1232
+ ```typescript
1233
+ const silentRefresh = createSilentRefresh(async () => {
1234
+ // 刷新token逻辑
1235
+ await refreshToken();
1236
+ });
1237
+
1238
+ // 使用示例
1239
+ silentRefresh(
1240
+ () => console.log("刷新成功"),
1241
+ () => console.log("刷新失败")
1242
+ );
1243
+ ```
1244
+
1245
+ #### soonFetch
1246
+
1247
+ 一个轻量级的 fetch 包装器,支持缓存、共享和竞态条件处理。
1248
+
1249
+ **参数:**
1250
+
1251
+ - `config`: 配置对象
1252
+ - `url`: 请求 URL
1253
+ - `options`: 请求选项 (SoonOptions)
1254
+ - `baseURL`: 基础 URL
1255
+ - `baseOptions`: 基础请求选项
1256
+ - `store`: 自定义请求存储
1257
+ - `sortRequestKey`: 是否对请求键进行排序
1258
+
1259
+ **返回:** 解析为响应的 Promise
1260
+
1261
+ **示例:**
1262
+
1263
+ ```typescript
1264
+ const data = await soonFetch<User[]>({
1265
+ url: "/api/users",
1266
+ options: {
1267
+ method: "GET",
1268
+ query: { page: 1 },
1269
+ share: true,
1270
+ staleTime: 5000,
1271
+ },
1272
+ baseURL: "https://api.example.com",
1273
+ });
1274
+ ```
1275
+
1276
+ #### parseWithBase
1277
+
1278
+ 解析基础 URL 配置。
1279
+ 处理 baseURL、headers、body 等配置项,生成最终的请求配置。
1280
+
1281
+ **参数:**
1282
+
1283
+ - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
1284
+
1285
+ **返回:** 处理后的 url、options、is_body_json 和 abortController
1286
+
1287
+ **示例:**
1288
+
1289
+ ```typescript
1290
+ const result = parseWithBase({
1291
+ url: "/api/users",
1292
+ options: { method: "GET" },
1293
+ baseURL: "https://api.example.com",
1294
+ });
1295
+ ```
1296
+
1297
+ #### toFormData
1298
+
1299
+ 将对象转换为 FormData。
1300
+ 用于将普通对象转换为 FormData 格式,支持文件和普通值。
1301
+
1302
+ **参数:**
1303
+
1304
+ - `body`: 要转换的对象
1305
+
1306
+ **返回:** 转换后的 FormData 对象
1307
+
1308
+ **示例:**
1309
+
1310
+ ```typescript
1311
+ const formData = toFormData({
1312
+ name: "John",
1313
+ avatar: fileBlob,
1314
+ age: 30,
1315
+ });
1316
+ ```
1317
+
1318
+ #### progressDownload
1319
+
1320
+ 带进度的下载。
1321
+ 用于下载文件并跟踪进度。
1322
+
1323
+ **参数:**
1324
+
1325
+ - `response`: 响应对象
1326
+ - `onProgress`: 进度回调函数
1327
+
1328
+ **返回:** 解析为 ArrayBuffer 的 Promise
1329
+
1330
+ **示例:**
1331
+
1332
+ ```typescript
1333
+ const response = await fetch("/api/download");
1334
+ const buffer = await progressDownload(response, (progress, downloaded, total) => {
1335
+ console.log(`进度: ${progress}%, 已下载: ${downloaded}/${total}`);
1336
+ });
1337
+ ```
1338
+
1339
+ #### progressReadBody
1340
+
1341
+ 带进度的读取响应体。
1342
+ 用于读取响应体并跟踪进度。
1343
+
1344
+ **参数:**
1345
+
1346
+ - `body`: ReadableStream<Uint8Array>
1347
+ - `onProgress`: 进度回调函数
1348
+ - `total`: 总大小(字节,默认: 0)
1349
+
1350
+ **返回:** 解析为 ArrayBuffer 的 Promise
1351
+
1352
+ **示例:**
1353
+
1354
+ ```typescript
1355
+ const response = await fetch("/api/download");
1356
+ const buffer = await progressReadBody(response.body, (progress, downloaded, total) => {
1357
+ console.log(`进度: ${progress}%, 已下载: ${downloaded}/${total}`);
1358
+ });
1359
+ ```
1360
+
1361
+ #### parseOptions
1362
+
1363
+ 解析和合并请求选项。
1364
+ 用于处理请求选项,包括 baseURL、headers 和 body。
1365
+
1366
+ **参数:**
1367
+
1368
+ - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
1369
+
1370
+ **返回:** 包含解析后的 url、options、is_body_json 和 abortController 的对象
1371
+
1372
+ **示例:**
1373
+
1374
+ ```typescript
1375
+ const parsed = parseOptions({
1376
+ url: "/api/users",
1377
+ options: { method: "GET", query: { page: 1 } },
1378
+ baseURL: "https://api.example.com",
1379
+ baseOptions: { timeout: 5000 },
1380
+ });
1381
+ ```
1382
+
1383
+ #### requestWithStore
1384
+
1385
+ 带存储支持的请求包装器。
1386
+ 用于处理带有缓存、共享和竞态条件处理的请求。
1387
+
1388
+ **参数:**
1389
+
1390
+ - `store`: 请求存储实例
1391
+ - `requestFn`: 请求函数
1392
+ - `requestKey`: 请求键
1393
+ - `fetchAbort`: AbortController
1394
+ - `options`: 选项对象
1395
+
1396
+ **返回:** 解析为响应的 Promise
1397
+
1398
+ **示例:**
1399
+
1400
+ ```typescript
1401
+ const store = createRequestStore();
1402
+ const data = await requestWithStore(store, () => fetch(url, options), requestKey, abortController, {
1403
+ share: true,
1404
+ staleTime: 5000,
1405
+ });
1406
+ ```
1407
+
1408
+
1409
+ [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
1410
+
1411
+ <!-- omit in toc -->
1412
+
1413
+ ##### 安装 Installation
1414
+
1415
+ ```bash
1416
+ npm install soon-fetch
1417
+ ```