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 +1417 -1417
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +480 -0
- package/dist/index.d.mts +480 -0
- package/dist/index.mjs +1 -0
- package/package.json +36 -45
- package/dist/index.cjs.js +0 -1
- package/dist/index.d.ts +0 -335
- package/dist/index.js +0 -1
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
|
+
```
|