@nickyzj2023/utils 1.0.10 → 1.0.12

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/src/network.ts CHANGED
@@ -1,101 +1,106 @@
1
- import { isObject } from "./is";
2
- import LRUCache from "./lru-cache";
3
-
4
- const cachedRequests = new LRUCache<string, Promise<Response>>();
5
-
6
- /**
7
- * 基于 Fetch API 的请求客户端
8
- * @param baseURL 接口前缀,如 https://nickyzj.run:3030,也可以不填
9
- * @param defaultOptions 客户端级别的请求选项,方法级别的选项会覆盖这里的相同值
10
- *
11
- * @remarks
12
- * 特性:
13
- * - body 里直接传递对象
14
- * - 能够缓存 GET 请求
15
- *
16
- * @example
17
- * // 用法1:创建客户端
18
- * const api = fetcher("https://nickyzj.run:3030", { headers: { Authorization: "Bearer token" } });
19
- * const res = await api.get<Blog>("/blogs/hello-world");
20
- *
21
- * // 用法2:直接发送请求
22
- * const res = await fetcher().get<Blog>("https://nickyzj.run:3030/blogs/hello-world");
23
- */
24
- export const fetcher = (baseURL = "", defaultOptions: RequestInit = {}) => {
25
- const createRequest = async <T>(path: string, options: RequestInit = {}) => {
26
- // 构建完整 URL
27
- const url = baseURL ? `${baseURL}${path}` : path;
28
-
29
- // 处理 body 为对象的情况
30
- if (isObject(options.body)) {
31
- options.body = JSON.stringify(options.body);
32
- options.headers = {
33
- ...defaultOptions.headers,
34
- ...options.headers,
35
- "Content-Type": "application/json",
36
- };
37
- }
38
-
39
- const request = () => fetch(url, options);
40
- const canCache = options.method === "GET" || !options.method;
41
- let promise: Promise<Response>;
42
- if (!canCache) {
43
- promise = request();
44
- } else {
45
- let tempPromise = cachedRequests.get(url);
46
- if (!tempPromise) {
47
- tempPromise = request();
48
- cachedRequests.set(url, tempPromise);
49
- }
50
- promise = tempPromise;
51
- }
52
-
53
- // 必须使用 clone() 消费一个新的响应体,否则下次从 cache 中获取的响应体会报错(无法被重复消费)
54
- const response = (await promise).clone();
55
- if (!response.ok) {
56
- throw new Error(response.statusText);
57
- }
58
-
59
- const data = await response.json();
60
- return data as T;
61
- };
62
-
63
- return {
64
- get: <T>(url: string, options: Omit<RequestInit, "method"> = {}) =>
65
- createRequest<T>(url, { ...options, method: "GET" }),
66
-
67
- post: <T>(
68
- url: string,
69
- body?: any,
70
- options: Omit<RequestInit, "method" | "body"> = {},
71
- ) => createRequest<T>(url, { ...options, method: "POST", body }),
72
-
73
- put: <T>(
74
- url: string,
75
- body?: any,
76
- options: Omit<RequestInit, "method" | "body"> = {},
77
- ) => createRequest<T>(url, { ...options, method: "PUT", body }),
78
-
79
- delete: <T>(url: string, options: Omit<RequestInit, "method"> = {}) =>
80
- createRequest<T>(url, { ...options, method: "DELETE" }),
81
- };
82
- };
83
-
84
- /**
85
- * Go 语言风格的异步处理方式
86
- * @param promise 一个能被 await 的异步函数
87
- * @returns 如果成功,返回 [null, 异步函数结果],否则返回 [Error, undefined]
88
- *
89
- * @example
90
- * const [error, response] = await to(fetcher().get<Blog>("/blogs/hello-world"));
91
- */
92
- export const to = async <T, U = Error>(
93
- promise: Promise<T>,
94
- ): Promise<[null, T] | [U, undefined]> => {
95
- try {
96
- const response = await promise;
97
- return [null, response];
98
- } catch (error) {
99
- return [error as U, undefined];
100
- }
101
- };
1
+ import { isObject } from "./is";
2
+ import { mergeObjects } from "./object";
3
+
4
+ /**
5
+ * 基于 Fetch API 的请求客户端
6
+ * @param baseURL 接口前缀,如 https://nickyzj.run:3030,也可以不填
7
+ * @param defaultOptions 客户端级别的请求选项,方法级别的选项会覆盖这里的相同值
8
+ *
9
+ * @remarks
10
+ * 特性:
11
+ * - 合并客户端级别、方法级别的请求选项
12
+ * - 在 body 里直接传递对象
13
+ * - 可选择使用 to() 处理返回结果为 [Error, Response]
14
+ * - 可选择使用 withCache() 缓存请求结果
15
+ *
16
+ * @example
17
+ * // 用法1:创建客户端
18
+ * const api = fetcher("https://nickyzj.run:3030", { headers: { Authorization: "Bearer token" } });
19
+ * const res = await api.get<Blog>("/blogs/hello-world");
20
+ *
21
+ * // 用法2:直接发送请求
22
+ * const res = await fetcher().get<Blog>("https://nickyzj.run:3030/blogs/hello-world");
23
+ *
24
+ * // 安全处理返回结果
25
+ * const [error, data] = await to(api.get<Blog>("/blogs/hello-world"));
26
+ * if (error) {
27
+ * console.error(error);
28
+ * return;
29
+ * }
30
+ *
31
+ * // 缓存请求结果
32
+ * const getBlogs = withCache(api.get);
33
+ * await getBlogs("/blogs");
34
+ * await sleep(1000);
35
+ * await getBlogs("/blogs"); // 不请求,使用缓存结果
36
+ */
37
+ export const fetcher = (baseURL = "", defaultOptions: RequestInit = {}) => {
38
+ const createRequest = async <T>(
39
+ path: string,
40
+ requestOptions: RequestInit = {},
41
+ ) => {
42
+ // 构建完整 URL
43
+ const url = baseURL ? `${baseURL}${path}` : path;
44
+
45
+ // 合并 options
46
+ const options = mergeObjects(defaultOptions, requestOptions);
47
+
48
+ // 转换 body 为字符串
49
+ if (isObject(options.body)) {
50
+ options.body = JSON.stringify(options.body);
51
+ options.headers = {
52
+ "Content-Type": "application/json",
53
+ };
54
+ }
55
+
56
+ // 发送请求
57
+ const response = await fetch(url, options);
58
+ if (!response.ok) {
59
+ throw new Error(response.statusText);
60
+ }
61
+
62
+ const data = await response.json();
63
+ return data as T;
64
+ };
65
+
66
+ return {
67
+ get: <T>(url: string, options: Omit<RequestInit, "method"> = {}) =>
68
+ createRequest<T>(url, { ...options, method: "GET" }),
69
+
70
+ post: <T>(
71
+ url: string,
72
+ body: any,
73
+ options: Omit<RequestInit, "method" | "body"> = {},
74
+ ) => createRequest<T>(url, { ...options, method: "POST", body }),
75
+
76
+ put: <T>(
77
+ url: string,
78
+ body: any,
79
+ options: Omit<RequestInit, "method" | "body"> = {},
80
+ ) => createRequest<T>(url, { ...options, method: "PUT", body }),
81
+
82
+ delete: <T>(
83
+ url: string,
84
+ options: Omit<RequestInit, "method" | "body"> = {},
85
+ ) => createRequest<T>(url, { ...options, method: "DELETE" }),
86
+ };
87
+ };
88
+
89
+ /**
90
+ * Go 语言风格的异步处理方式
91
+ * @param promise 一个能被 await 的异步函数
92
+ * @returns 如果成功,返回 [null, 异步函数结果],否则返回 [Error, undefined]
93
+ *
94
+ * @example
95
+ * const [error, response] = await to(fetcher().get<Blog>("/blogs/hello-world"));
96
+ */
97
+ export const to = async <T, U = Error>(
98
+ promise: Promise<T>,
99
+ ): Promise<[null, T] | [U, undefined]> => {
100
+ try {
101
+ const response = await promise;
102
+ return [null, response];
103
+ } catch (error) {
104
+ return [error as U, undefined];
105
+ }
106
+ };
package/src/object.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { isObject } from "./is";
2
+
3
+ export const mergeObjects = (...objects: Record<string, any>[]) => {
4
+ return objects.reduce(
5
+ (acc: Record<string, any>, obj: Record<string, any>) => {
6
+ if (!isObject(obj)) {
7
+ return acc;
8
+ }
9
+
10
+ Object.keys(obj).forEach((key) => {
11
+ const accValue = acc[key];
12
+ const objValue = obj[key];
13
+
14
+ // 如果两个值都是数组,则合并数组
15
+ if (Array.isArray(accValue) && Array.isArray(objValue)) {
16
+ acc[key] = [...accValue, ...objValue];
17
+ }
18
+ // 如果两个值都是对象,则递归合并
19
+ else if (isObject(objValue) && isObject(accValue)) {
20
+ acc[key] = mergeObjects(accValue, objValue);
21
+ }
22
+ // 其他情况直接覆盖
23
+ else {
24
+ acc[key] = objValue;
25
+ }
26
+ });
27
+
28
+ return acc;
29
+ },
30
+ {},
31
+ );
32
+ };
package/src/time.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 延迟一段时间再执行后续代码
3
+ * @param time 延迟时间,默认 150ms
4
+ * @example
5
+ * await sleep(1000); // 等待 1 秒执行后续代码
6
+ */
7
+ export const sleep = async (time = 150) => {
8
+ return new Promise((resolve) => {
9
+ setTimeout(resolve, time);
10
+ });
11
+ };