@vlian/framework 1.1.0 → 1.2.1
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 +35 -1
- package/dist/core/config/ConfigLoader.cjs +90 -8
- package/dist/core/config/ConfigLoader.cjs.map +1 -1
- package/dist/core/config/ConfigLoader.d.ts +20 -4
- package/dist/core/config/ConfigLoader.d.ts.map +1 -1
- package/dist/core/config/ConfigLoader.js +90 -8
- package/dist/core/config/ConfigLoader.js.map +1 -1
- package/dist/core/error/ErrorBoundary.cjs +23 -9
- package/dist/core/error/ErrorBoundary.cjs.map +1 -1
- package/dist/core/error/ErrorBoundary.d.ts.map +1 -1
- package/dist/core/error/ErrorBoundary.js +23 -9
- package/dist/core/error/ErrorBoundary.js.map +1 -1
- package/dist/core/error/ErrorHandler.cjs +277 -0
- package/dist/core/error/ErrorHandler.cjs.map +1 -0
- package/dist/core/error/ErrorHandler.d.ts +172 -0
- package/dist/core/error/ErrorHandler.d.ts.map +1 -0
- package/dist/core/error/ErrorHandler.js +261 -0
- package/dist/core/error/ErrorHandler.js.map +1 -0
- package/dist/core/error/index.cjs +10 -0
- package/dist/core/error/index.cjs.map +1 -1
- package/dist/core/error/index.d.ts +2 -0
- package/dist/core/error/index.d.ts.map +1 -1
- package/dist/core/error/index.js +1 -0
- package/dist/core/error/index.js.map +1 -1
- package/dist/core/startup/initializeServices.cjs +69 -24
- package/dist/core/startup/initializeServices.cjs.map +1 -1
- package/dist/core/startup/initializeServices.d.ts +9 -2
- package/dist/core/startup/initializeServices.d.ts.map +1 -1
- package/dist/core/startup/initializeServices.js +73 -26
- package/dist/core/startup/initializeServices.js.map +1 -1
- package/dist/core/startup/renderApp.cjs +100 -61
- package/dist/core/startup/renderApp.cjs.map +1 -1
- package/dist/core/startup/renderApp.d.ts +2 -2
- package/dist/core/startup/renderApp.d.ts.map +1 -1
- package/dist/core/startup/renderApp.js +101 -62
- package/dist/core/startup/renderApp.js.map +1 -1
- package/dist/core/startup/startApp.cjs +7 -0
- package/dist/core/startup/startApp.cjs.map +1 -1
- package/dist/core/startup/startApp.d.ts.map +1 -1
- package/dist/core/startup/startApp.js +7 -0
- package/dist/core/startup/startApp.js.map +1 -1
- package/dist/index.umd.js +32434 -39098
- package/dist/index.umd.js.map +1 -1
- package/dist/request/core/RequestClient.cjs +20 -1
- package/dist/request/core/RequestClient.cjs.map +1 -1
- package/dist/request/core/RequestClient.d.ts +8 -0
- package/dist/request/core/RequestClient.d.ts.map +1 -1
- package/dist/request/core/RequestClient.js +20 -1
- package/dist/request/core/RequestClient.js.map +1 -1
- package/dist/request/index.cjs +3 -0
- package/dist/request/index.cjs.map +1 -1
- package/dist/request/index.d.ts +2 -2
- package/dist/request/index.d.ts.map +1 -1
- package/dist/request/index.js +1 -1
- package/dist/request/index.js.map +1 -1
- package/dist/request/plugin/index.cjs +4 -0
- package/dist/request/plugin/index.cjs.map +1 -1
- package/dist/request/plugin/index.d.ts +1 -0
- package/dist/request/plugin/index.d.ts.map +1 -1
- package/dist/request/plugin/index.js +1 -0
- package/dist/request/plugin/index.js.map +1 -1
- package/dist/request/plugin/queue.cjs +140 -0
- package/dist/request/plugin/queue.cjs.map +1 -0
- package/dist/request/plugin/queue.d.ts +92 -0
- package/dist/request/plugin/queue.d.ts.map +1 -0
- package/dist/request/plugin/queue.js +156 -0
- package/dist/request/plugin/queue.js.map +1 -0
- package/dist/request/utils/RequestQueueManager.cjs +168 -0
- package/dist/request/utils/RequestQueueManager.cjs.map +1 -0
- package/dist/request/utils/RequestQueueManager.d.ts +75 -0
- package/dist/request/utils/RequestQueueManager.d.ts.map +1 -0
- package/dist/request/utils/RequestQueueManager.js +160 -0
- package/dist/request/utils/RequestQueueManager.js.map +1 -0
- package/dist/request/utils/index.cjs +4 -0
- package/dist/request/utils/index.cjs.map +1 -1
- package/dist/request/utils/index.d.ts +1 -0
- package/dist/request/utils/index.d.ts.map +1 -1
- package/dist/request/utils/index.js +1 -0
- package/dist/request/utils/index.js.map +1 -1
- package/package.json +40 -6
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求队列插件
|
|
3
|
+
*
|
|
4
|
+
* 当检测到特定的响应 code 或 status 时,暂停后续请求并放入队列,
|
|
5
|
+
* 执行回调方法,等待回调完成后继续执行队列中的请求
|
|
6
|
+
*/ "use strict";
|
|
7
|
+
Object.defineProperty(exports, "__esModule", {
|
|
8
|
+
value: true
|
|
9
|
+
});
|
|
10
|
+
Object.defineProperty(exports, "createQueuePlugin", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
get: function() {
|
|
13
|
+
return createQueuePlugin;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const _RequestPlugin = require("./RequestPlugin");
|
|
17
|
+
const _RequestQueueManager = require("../utils/RequestQueueManager");
|
|
18
|
+
/**
|
|
19
|
+
* 检查响应是否匹配触发条件
|
|
20
|
+
*/ function matchesTrigger(response, context, trigger) {
|
|
21
|
+
// 检查自定义检查函数
|
|
22
|
+
if (trigger.check) {
|
|
23
|
+
return trigger.check(response, context);
|
|
24
|
+
}
|
|
25
|
+
// 检查响应数据中的 code 字段
|
|
26
|
+
if (trigger.code !== undefined) {
|
|
27
|
+
const responseData = response.data;
|
|
28
|
+
if (responseData && typeof responseData === 'object' && 'code' in responseData) {
|
|
29
|
+
const code = responseData.code;
|
|
30
|
+
if (typeof trigger.code === 'function') {
|
|
31
|
+
if (trigger.code(code)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
} else if (code === trigger.code) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// 检查 HTTP 状态码
|
|
40
|
+
if (trigger.status !== undefined) {
|
|
41
|
+
const status = response.status;
|
|
42
|
+
if (typeof trigger.status === 'function') {
|
|
43
|
+
if (trigger.status(status)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
} else if (Array.isArray(trigger.status)) {
|
|
47
|
+
if (trigger.status.includes(status)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
} else if (status === trigger.status) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 检查响应是否匹配任一触发条件
|
|
58
|
+
*/ function matchesAnyTrigger(response, context, triggers) {
|
|
59
|
+
return triggers.some((trigger)=>matchesTrigger(response, context, trigger));
|
|
60
|
+
}
|
|
61
|
+
function createQueuePlugin(config = {}) {
|
|
62
|
+
const { triggers = [], onTrigger, enabled = true, checkOnError = true, queueTimeout = 30000 } = config;
|
|
63
|
+
if (triggers.length === 0) {
|
|
64
|
+
// 如果没有配置触发条件,返回一个空插件
|
|
65
|
+
return {
|
|
66
|
+
name: 'queue',
|
|
67
|
+
priority: _RequestPlugin.PluginPriority.HIGH,
|
|
68
|
+
onBeforeRequest: undefined,
|
|
69
|
+
onAfterRequest: undefined,
|
|
70
|
+
onError: undefined
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const queueManager = (0, _RequestQueueManager.getRequestQueueManager)();
|
|
74
|
+
return {
|
|
75
|
+
name: 'queue',
|
|
76
|
+
priority: _RequestPlugin.PluginPriority.HIGH,
|
|
77
|
+
async onAfterRequest (context) {
|
|
78
|
+
if (!enabled || !context.response) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// 检查是否匹配触发条件
|
|
82
|
+
const matched = matchesAnyTrigger(context.response, context, triggers);
|
|
83
|
+
if (matched && onTrigger) {
|
|
84
|
+
// 暂停队列(后续请求会被 RequestClient 自动放入队列)
|
|
85
|
+
queueManager.pause();
|
|
86
|
+
// 设置队列超时
|
|
87
|
+
const timeoutId = setTimeout(()=>{
|
|
88
|
+
queueManager.clear('队列超时');
|
|
89
|
+
}, queueTimeout);
|
|
90
|
+
try {
|
|
91
|
+
// 执行回调并等待完成,然后恢复队列
|
|
92
|
+
await queueManager.processAndResume(async ()=>{
|
|
93
|
+
await onTrigger(context.response, context);
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
// 回调执行失败,清空队列
|
|
97
|
+
queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
+
// 不抛出错误,让请求正常返回(但队列已被清空)
|
|
99
|
+
} finally{
|
|
100
|
+
clearTimeout(timeoutId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
async onError (context) {
|
|
105
|
+
if (!enabled || !checkOnError || !context.error) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// 如果有响应,检查是否匹配触发条件
|
|
109
|
+
if (context.response) {
|
|
110
|
+
const matched = matchesAnyTrigger(context.response, context, triggers);
|
|
111
|
+
if (matched && onTrigger) {
|
|
112
|
+
// 暂停队列(后续请求会被 RequestClient 自动放入队列)
|
|
113
|
+
queueManager.pause();
|
|
114
|
+
// 设置队列超时
|
|
115
|
+
const timeoutId = setTimeout(()=>{
|
|
116
|
+
queueManager.clear('队列超时');
|
|
117
|
+
}, queueTimeout);
|
|
118
|
+
try {
|
|
119
|
+
// 执行回调并等待完成,然后恢复队列
|
|
120
|
+
await queueManager.processAndResume(async ()=>{
|
|
121
|
+
await onTrigger(context.response, context);
|
|
122
|
+
});
|
|
123
|
+
// 错误已被处理,返回 true 阻止错误传播
|
|
124
|
+
return true;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// 回调执行失败,清空队列
|
|
127
|
+
queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
128
|
+
// 不处理错误,继续传播
|
|
129
|
+
return false;
|
|
130
|
+
} finally{
|
|
131
|
+
clearTimeout(timeoutId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/request/plugin/queue.ts"],"sourcesContent":["/**\n * 请求队列插件\n * \n * 当检测到特定的响应 code 或 status 时,暂停后续请求并放入队列,\n * 执行回调方法,等待回调完成后继续执行队列中的请求\n */\n\nimport type { RequestPlugin } from './RequestPlugin';\nimport type { RequestContext, Response } from '../types';\nimport { PluginPriority } from './RequestPlugin';\nimport { getRequestQueueManager } from '../utils/RequestQueueManager';\n\n/**\n * 触发条件配置\n */\nexport interface QueueTriggerCondition {\n /**\n * 响应数据中的 code 字段值(业务状态码)\n * 如果响应数据是对象且包含 code 字段,且值匹配则触发\n */\n code?: number | string | ((code: unknown) => boolean);\n \n /**\n * HTTP 状态码\n * 如果 HTTP status 匹配则触发\n */\n status?: number | number[] | ((status: number) => boolean);\n \n /**\n * 自定义检查函数\n * 返回 true 表示应该触发队列暂停\n */\n check?: (response: Response, context: RequestContext) => boolean;\n}\n\n/**\n * 队列插件配置\n */\nexport interface QueuePluginConfig {\n /**\n * 触发条件列表\n * 当响应匹配任一条件时,会暂停队列并执行回调\n */\n triggers?: QueueTriggerCondition[];\n \n /**\n * 回调函数\n * 当检测到触发条件时执行,返回 Promise 表示异步操作\n * \n * @param response - 触发条件的响应\n * @param context - 请求上下文\n * @returns Promise 或 void\n */\n onTrigger?: (response: Response, context: RequestContext) => Promise<void> | void;\n \n /**\n * 是否启用队列功能\n * @default true\n */\n enabled?: boolean;\n \n /**\n * 是否在错误时也检查触发条件\n * @default true\n */\n checkOnError?: boolean;\n \n /**\n * 队列超时时间(毫秒)\n * 如果队列暂停超过此时间,会自动恢复并拒绝队列中的请求\n * @default 30000 (30秒)\n */\n queueTimeout?: number;\n}\n\n/**\n * 检查响应是否匹配触发条件\n */\nfunction matchesTrigger(\n response: Response,\n context: RequestContext,\n trigger: QueueTriggerCondition\n): boolean {\n // 检查自定义检查函数\n if (trigger.check) {\n return trigger.check(response, context);\n }\n \n // 检查响应数据中的 code 字段\n if (trigger.code !== undefined) {\n const responseData = response.data;\n if (responseData && typeof responseData === 'object' && 'code' in responseData) {\n const code = (responseData as { code: unknown }).code;\n \n if (typeof trigger.code === 'function') {\n if (trigger.code(code)) {\n return true;\n }\n } else if (code === trigger.code) {\n return true;\n }\n }\n }\n \n // 检查 HTTP 状态码\n if (trigger.status !== undefined) {\n const status = response.status;\n \n if (typeof trigger.status === 'function') {\n if (trigger.status(status)) {\n return true;\n }\n } else if (Array.isArray(trigger.status)) {\n if (trigger.status.includes(status)) {\n return true;\n }\n } else if (status === trigger.status) {\n return true;\n }\n }\n \n return false;\n}\n\n/**\n * 检查响应是否匹配任一触发条件\n */\nfunction matchesAnyTrigger(\n response: Response,\n context: RequestContext,\n triggers: QueueTriggerCondition[]\n): boolean {\n return triggers.some((trigger) => matchesTrigger(response, context, trigger));\n}\n\n/**\n * 创建请求队列插件\n * \n * @param config - 队列插件配置\n * @returns 队列插件实例\n * \n * @example\n * ```typescript\n * import { createRequestClient } from '@vlian/framework/request';\n * import { createQueuePlugin } from '@vlian/framework/request/plugin';\n * \n * const client = await createRequestClient({\n * plugins: [\n * createQueuePlugin({\n * triggers: [\n * { code: 401 }, // 业务 code 401 表示未授权\n * { status: 401 }, // HTTP 401 未授权\n * ],\n * onTrigger: async (response, context) => {\n * // 刷新 token 或跳转登录\n * await refreshToken();\n * },\n * }),\n * ],\n * });\n * ```\n */\nexport function createQueuePlugin(config: QueuePluginConfig = {}): RequestPlugin {\n const {\n triggers = [],\n onTrigger,\n enabled = true,\n checkOnError = true,\n queueTimeout = 30000,\n } = config;\n \n if (triggers.length === 0) {\n // 如果没有配置触发条件,返回一个空插件\n return {\n name: 'queue',\n priority: PluginPriority.HIGH,\n onBeforeRequest: undefined,\n onAfterRequest: undefined,\n onError: undefined,\n };\n }\n \n const queueManager = getRequestQueueManager();\n \n return {\n name: 'queue',\n priority: PluginPriority.HIGH, // 高优先级,确保在其他插件之前执行\n \n async onAfterRequest(context: RequestContext): Promise<Response | void> {\n if (!enabled || !context.response) {\n return;\n }\n \n // 检查是否匹配触发条件\n const matched = matchesAnyTrigger(context.response, context, triggers);\n \n if (matched && onTrigger) {\n // 暂停队列(后续请求会被 RequestClient 自动放入队列)\n queueManager.pause();\n \n // 设置队列超时\n const timeoutId = setTimeout(() => {\n queueManager.clear('队列超时');\n }, queueTimeout);\n \n try {\n // 执行回调并等待完成,然后恢复队列\n await queueManager.processAndResume(async () => {\n await onTrigger(context.response!, context);\n });\n } catch (error) {\n // 回调执行失败,清空队列\n queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);\n // 不抛出错误,让请求正常返回(但队列已被清空)\n } finally {\n clearTimeout(timeoutId);\n }\n }\n },\n \n async onError(context: RequestContext): Promise<boolean | void> {\n if (!enabled || !checkOnError || !context.error) {\n return;\n }\n \n // 如果有响应,检查是否匹配触发条件\n if (context.response) {\n const matched = matchesAnyTrigger(context.response, context, triggers);\n \n if (matched && onTrigger) {\n // 暂停队列(后续请求会被 RequestClient 自动放入队列)\n queueManager.pause();\n \n // 设置队列超时\n const timeoutId = setTimeout(() => {\n queueManager.clear('队列超时');\n }, queueTimeout);\n \n try {\n // 执行回调并等待完成,然后恢复队列\n await queueManager.processAndResume(async () => {\n await onTrigger(context.response!, context);\n });\n \n // 错误已被处理,返回 true 阻止错误传播\n return true;\n } catch (error) {\n // 回调执行失败,清空队列\n queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);\n // 不处理错误,继续传播\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n }\n \n return false;\n },\n };\n}\n"],"names":["createQueuePlugin","matchesTrigger","response","context","trigger","check","code","undefined","responseData","data","status","Array","isArray","includes","matchesAnyTrigger","triggers","some","config","onTrigger","enabled","checkOnError","queueTimeout","length","name","priority","PluginPriority","HIGH","onBeforeRequest","onAfterRequest","onError","queueManager","getRequestQueueManager","matched","pause","timeoutId","setTimeout","clear","processAndResume","error","Error","message","String","clearTimeout"],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求队列插件
|
|
3
|
+
*
|
|
4
|
+
* 当检测到特定的响应 code 或 status 时,暂停后续请求并放入队列,
|
|
5
|
+
* 执行回调方法,等待回调完成后继续执行队列中的请求
|
|
6
|
+
*/
|
|
7
|
+
import type { RequestPlugin } from './RequestPlugin';
|
|
8
|
+
import type { RequestContext, Response } from '../types';
|
|
9
|
+
/**
|
|
10
|
+
* 触发条件配置
|
|
11
|
+
*/
|
|
12
|
+
export interface QueueTriggerCondition {
|
|
13
|
+
/**
|
|
14
|
+
* 响应数据中的 code 字段值(业务状态码)
|
|
15
|
+
* 如果响应数据是对象且包含 code 字段,且值匹配则触发
|
|
16
|
+
*/
|
|
17
|
+
code?: number | string | ((code: unknown) => boolean);
|
|
18
|
+
/**
|
|
19
|
+
* HTTP 状态码
|
|
20
|
+
* 如果 HTTP status 匹配则触发
|
|
21
|
+
*/
|
|
22
|
+
status?: number | number[] | ((status: number) => boolean);
|
|
23
|
+
/**
|
|
24
|
+
* 自定义检查函数
|
|
25
|
+
* 返回 true 表示应该触发队列暂停
|
|
26
|
+
*/
|
|
27
|
+
check?: (response: Response, context: RequestContext) => boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 队列插件配置
|
|
31
|
+
*/
|
|
32
|
+
export interface QueuePluginConfig {
|
|
33
|
+
/**
|
|
34
|
+
* 触发条件列表
|
|
35
|
+
* 当响应匹配任一条件时,会暂停队列并执行回调
|
|
36
|
+
*/
|
|
37
|
+
triggers?: QueueTriggerCondition[];
|
|
38
|
+
/**
|
|
39
|
+
* 回调函数
|
|
40
|
+
* 当检测到触发条件时执行,返回 Promise 表示异步操作
|
|
41
|
+
*
|
|
42
|
+
* @param response - 触发条件的响应
|
|
43
|
+
* @param context - 请求上下文
|
|
44
|
+
* @returns Promise 或 void
|
|
45
|
+
*/
|
|
46
|
+
onTrigger?: (response: Response, context: RequestContext) => Promise<void> | void;
|
|
47
|
+
/**
|
|
48
|
+
* 是否启用队列功能
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* 是否在错误时也检查触发条件
|
|
54
|
+
* @default true
|
|
55
|
+
*/
|
|
56
|
+
checkOnError?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* 队列超时时间(毫秒)
|
|
59
|
+
* 如果队列暂停超过此时间,会自动恢复并拒绝队列中的请求
|
|
60
|
+
* @default 30000 (30秒)
|
|
61
|
+
*/
|
|
62
|
+
queueTimeout?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 创建请求队列插件
|
|
66
|
+
*
|
|
67
|
+
* @param config - 队列插件配置
|
|
68
|
+
* @returns 队列插件实例
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import { createRequestClient } from '@vlian/framework/request';
|
|
73
|
+
* import { createQueuePlugin } from '@vlian/framework/request/plugin';
|
|
74
|
+
*
|
|
75
|
+
* const client = await createRequestClient({
|
|
76
|
+
* plugins: [
|
|
77
|
+
* createQueuePlugin({
|
|
78
|
+
* triggers: [
|
|
79
|
+
* { code: 401 }, // 业务 code 401 表示未授权
|
|
80
|
+
* { status: 401 }, // HTTP 401 未授权
|
|
81
|
+
* ],
|
|
82
|
+
* onTrigger: async (response, context) => {
|
|
83
|
+
* // 刷新 token 或跳转登录
|
|
84
|
+
* await refreshToken();
|
|
85
|
+
* },
|
|
86
|
+
* }),
|
|
87
|
+
* ],
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function createQueuePlugin(config?: QueuePluginConfig): RequestPlugin;
|
|
92
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/request/plugin/queue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIzD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;IAEtD;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IAE3D;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC;CAClE;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAEnC;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAElF;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA8DD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,iBAAsB,GAAG,aAAa,CAkG/E"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求队列插件
|
|
3
|
+
*
|
|
4
|
+
* 当检测到特定的响应 code 或 status 时,暂停后续请求并放入队列,
|
|
5
|
+
* 执行回调方法,等待回调完成后继续执行队列中的请求
|
|
6
|
+
*/ import { PluginPriority } from "./RequestPlugin";
|
|
7
|
+
import { getRequestQueueManager } from "../utils/RequestQueueManager";
|
|
8
|
+
/**
|
|
9
|
+
* 检查响应是否匹配触发条件
|
|
10
|
+
*/ function matchesTrigger(response, context, trigger) {
|
|
11
|
+
// 检查自定义检查函数
|
|
12
|
+
if (trigger.check) {
|
|
13
|
+
return trigger.check(response, context);
|
|
14
|
+
}
|
|
15
|
+
// 检查响应数据中的 code 字段
|
|
16
|
+
if (trigger.code !== undefined) {
|
|
17
|
+
const responseData = response.data;
|
|
18
|
+
if (responseData && typeof responseData === 'object' && 'code' in responseData) {
|
|
19
|
+
const code = responseData.code;
|
|
20
|
+
if (typeof trigger.code === 'function') {
|
|
21
|
+
if (trigger.code(code)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
} else if (code === trigger.code) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 检查 HTTP 状态码
|
|
30
|
+
if (trigger.status !== undefined) {
|
|
31
|
+
const status = response.status;
|
|
32
|
+
if (typeof trigger.status === 'function') {
|
|
33
|
+
if (trigger.status(status)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
} else if (Array.isArray(trigger.status)) {
|
|
37
|
+
if (trigger.status.includes(status)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
} else if (status === trigger.status) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 检查响应是否匹配任一触发条件
|
|
48
|
+
*/ function matchesAnyTrigger(response, context, triggers) {
|
|
49
|
+
return triggers.some((trigger)=>matchesTrigger(response, context, trigger));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 创建请求队列插件
|
|
53
|
+
*
|
|
54
|
+
* @param config - 队列插件配置
|
|
55
|
+
* @returns 队列插件实例
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { createRequestClient } from '@vlian/framework/request';
|
|
60
|
+
* import { createQueuePlugin } from '@vlian/framework/request/plugin';
|
|
61
|
+
*
|
|
62
|
+
* const client = await createRequestClient({
|
|
63
|
+
* plugins: [
|
|
64
|
+
* createQueuePlugin({
|
|
65
|
+
* triggers: [
|
|
66
|
+
* { code: 401 }, // 业务 code 401 表示未授权
|
|
67
|
+
* { status: 401 }, // HTTP 401 未授权
|
|
68
|
+
* ],
|
|
69
|
+
* onTrigger: async (response, context) => {
|
|
70
|
+
* // 刷新 token 或跳转登录
|
|
71
|
+
* await refreshToken();
|
|
72
|
+
* },
|
|
73
|
+
* }),
|
|
74
|
+
* ],
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/ export function createQueuePlugin(config = {}) {
|
|
78
|
+
const { triggers = [], onTrigger, enabled = true, checkOnError = true, queueTimeout = 30000 } = config;
|
|
79
|
+
if (triggers.length === 0) {
|
|
80
|
+
// 如果没有配置触发条件,返回一个空插件
|
|
81
|
+
return {
|
|
82
|
+
name: 'queue',
|
|
83
|
+
priority: PluginPriority.HIGH,
|
|
84
|
+
onBeforeRequest: undefined,
|
|
85
|
+
onAfterRequest: undefined,
|
|
86
|
+
onError: undefined
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const queueManager = getRequestQueueManager();
|
|
90
|
+
return {
|
|
91
|
+
name: 'queue',
|
|
92
|
+
priority: PluginPriority.HIGH,
|
|
93
|
+
async onAfterRequest (context) {
|
|
94
|
+
if (!enabled || !context.response) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// 检查是否匹配触发条件
|
|
98
|
+
const matched = matchesAnyTrigger(context.response, context, triggers);
|
|
99
|
+
if (matched && onTrigger) {
|
|
100
|
+
// 暂停队列(后续请求会被 RequestClient 自动放入队列)
|
|
101
|
+
queueManager.pause();
|
|
102
|
+
// 设置队列超时
|
|
103
|
+
const timeoutId = setTimeout(()=>{
|
|
104
|
+
queueManager.clear('队列超时');
|
|
105
|
+
}, queueTimeout);
|
|
106
|
+
try {
|
|
107
|
+
// 执行回调并等待完成,然后恢复队列
|
|
108
|
+
await queueManager.processAndResume(async ()=>{
|
|
109
|
+
await onTrigger(context.response, context);
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// 回调执行失败,清空队列
|
|
113
|
+
queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
// 不抛出错误,让请求正常返回(但队列已被清空)
|
|
115
|
+
} finally{
|
|
116
|
+
clearTimeout(timeoutId);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
async onError (context) {
|
|
121
|
+
if (!enabled || !checkOnError || !context.error) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// 如果有响应,检查是否匹配触发条件
|
|
125
|
+
if (context.response) {
|
|
126
|
+
const matched = matchesAnyTrigger(context.response, context, triggers);
|
|
127
|
+
if (matched && onTrigger) {
|
|
128
|
+
// 暂停队列(后续请求会被 RequestClient 自动放入队列)
|
|
129
|
+
queueManager.pause();
|
|
130
|
+
// 设置队列超时
|
|
131
|
+
const timeoutId = setTimeout(()=>{
|
|
132
|
+
queueManager.clear('队列超时');
|
|
133
|
+
}, queueTimeout);
|
|
134
|
+
try {
|
|
135
|
+
// 执行回调并等待完成,然后恢复队列
|
|
136
|
+
await queueManager.processAndResume(async ()=>{
|
|
137
|
+
await onTrigger(context.response, context);
|
|
138
|
+
});
|
|
139
|
+
// 错误已被处理,返回 true 阻止错误传播
|
|
140
|
+
return true;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// 回调执行失败,清空队列
|
|
143
|
+
queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
|
+
// 不处理错误,继续传播
|
|
145
|
+
return false;
|
|
146
|
+
} finally{
|
|
147
|
+
clearTimeout(timeoutId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/request/plugin/queue.ts"],"sourcesContent":["/**\n * 请求队列插件\n * \n * 当检测到特定的响应 code 或 status 时,暂停后续请求并放入队列,\n * 执行回调方法,等待回调完成后继续执行队列中的请求\n */\n\nimport type { RequestPlugin } from './RequestPlugin';\nimport type { RequestContext, Response } from '../types';\nimport { PluginPriority } from './RequestPlugin';\nimport { getRequestQueueManager } from '../utils/RequestQueueManager';\n\n/**\n * 触发条件配置\n */\nexport interface QueueTriggerCondition {\n /**\n * 响应数据中的 code 字段值(业务状态码)\n * 如果响应数据是对象且包含 code 字段,且值匹配则触发\n */\n code?: number | string | ((code: unknown) => boolean);\n \n /**\n * HTTP 状态码\n * 如果 HTTP status 匹配则触发\n */\n status?: number | number[] | ((status: number) => boolean);\n \n /**\n * 自定义检查函数\n * 返回 true 表示应该触发队列暂停\n */\n check?: (response: Response, context: RequestContext) => boolean;\n}\n\n/**\n * 队列插件配置\n */\nexport interface QueuePluginConfig {\n /**\n * 触发条件列表\n * 当响应匹配任一条件时,会暂停队列并执行回调\n */\n triggers?: QueueTriggerCondition[];\n \n /**\n * 回调函数\n * 当检测到触发条件时执行,返回 Promise 表示异步操作\n * \n * @param response - 触发条件的响应\n * @param context - 请求上下文\n * @returns Promise 或 void\n */\n onTrigger?: (response: Response, context: RequestContext) => Promise<void> | void;\n \n /**\n * 是否启用队列功能\n * @default true\n */\n enabled?: boolean;\n \n /**\n * 是否在错误时也检查触发条件\n * @default true\n */\n checkOnError?: boolean;\n \n /**\n * 队列超时时间(毫秒)\n * 如果队列暂停超过此时间,会自动恢复并拒绝队列中的请求\n * @default 30000 (30秒)\n */\n queueTimeout?: number;\n}\n\n/**\n * 检查响应是否匹配触发条件\n */\nfunction matchesTrigger(\n response: Response,\n context: RequestContext,\n trigger: QueueTriggerCondition\n): boolean {\n // 检查自定义检查函数\n if (trigger.check) {\n return trigger.check(response, context);\n }\n \n // 检查响应数据中的 code 字段\n if (trigger.code !== undefined) {\n const responseData = response.data;\n if (responseData && typeof responseData === 'object' && 'code' in responseData) {\n const code = (responseData as { code: unknown }).code;\n \n if (typeof trigger.code === 'function') {\n if (trigger.code(code)) {\n return true;\n }\n } else if (code === trigger.code) {\n return true;\n }\n }\n }\n \n // 检查 HTTP 状态码\n if (trigger.status !== undefined) {\n const status = response.status;\n \n if (typeof trigger.status === 'function') {\n if (trigger.status(status)) {\n return true;\n }\n } else if (Array.isArray(trigger.status)) {\n if (trigger.status.includes(status)) {\n return true;\n }\n } else if (status === trigger.status) {\n return true;\n }\n }\n \n return false;\n}\n\n/**\n * 检查响应是否匹配任一触发条件\n */\nfunction matchesAnyTrigger(\n response: Response,\n context: RequestContext,\n triggers: QueueTriggerCondition[]\n): boolean {\n return triggers.some((trigger) => matchesTrigger(response, context, trigger));\n}\n\n/**\n * 创建请求队列插件\n * \n * @param config - 队列插件配置\n * @returns 队列插件实例\n * \n * @example\n * ```typescript\n * import { createRequestClient } from '@vlian/framework/request';\n * import { createQueuePlugin } from '@vlian/framework/request/plugin';\n * \n * const client = await createRequestClient({\n * plugins: [\n * createQueuePlugin({\n * triggers: [\n * { code: 401 }, // 业务 code 401 表示未授权\n * { status: 401 }, // HTTP 401 未授权\n * ],\n * onTrigger: async (response, context) => {\n * // 刷新 token 或跳转登录\n * await refreshToken();\n * },\n * }),\n * ],\n * });\n * ```\n */\nexport function createQueuePlugin(config: QueuePluginConfig = {}): RequestPlugin {\n const {\n triggers = [],\n onTrigger,\n enabled = true,\n checkOnError = true,\n queueTimeout = 30000,\n } = config;\n \n if (triggers.length === 0) {\n // 如果没有配置触发条件,返回一个空插件\n return {\n name: 'queue',\n priority: PluginPriority.HIGH,\n onBeforeRequest: undefined,\n onAfterRequest: undefined,\n onError: undefined,\n };\n }\n \n const queueManager = getRequestQueueManager();\n \n return {\n name: 'queue',\n priority: PluginPriority.HIGH, // 高优先级,确保在其他插件之前执行\n \n async onAfterRequest(context: RequestContext): Promise<Response | void> {\n if (!enabled || !context.response) {\n return;\n }\n \n // 检查是否匹配触发条件\n const matched = matchesAnyTrigger(context.response, context, triggers);\n \n if (matched && onTrigger) {\n // 暂停队列(后续请求会被 RequestClient 自动放入队列)\n queueManager.pause();\n \n // 设置队列超时\n const timeoutId = setTimeout(() => {\n queueManager.clear('队列超时');\n }, queueTimeout);\n \n try {\n // 执行回调并等待完成,然后恢复队列\n await queueManager.processAndResume(async () => {\n await onTrigger(context.response!, context);\n });\n } catch (error) {\n // 回调执行失败,清空队列\n queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);\n // 不抛出错误,让请求正常返回(但队列已被清空)\n } finally {\n clearTimeout(timeoutId);\n }\n }\n },\n \n async onError(context: RequestContext): Promise<boolean | void> {\n if (!enabled || !checkOnError || !context.error) {\n return;\n }\n \n // 如果有响应,检查是否匹配触发条件\n if (context.response) {\n const matched = matchesAnyTrigger(context.response, context, triggers);\n \n if (matched && onTrigger) {\n // 暂停队列(后续请求会被 RequestClient 自动放入队列)\n queueManager.pause();\n \n // 设置队列超时\n const timeoutId = setTimeout(() => {\n queueManager.clear('队列超时');\n }, queueTimeout);\n \n try {\n // 执行回调并等待完成,然后恢复队列\n await queueManager.processAndResume(async () => {\n await onTrigger(context.response!, context);\n });\n \n // 错误已被处理,返回 true 阻止错误传播\n return true;\n } catch (error) {\n // 回调执行失败,清空队列\n queueManager.clear(`回调执行失败: ${error instanceof Error ? error.message : String(error)}`);\n // 不处理错误,继续传播\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n }\n \n return false;\n },\n };\n}\n"],"names":["PluginPriority","getRequestQueueManager","matchesTrigger","response","context","trigger","check","code","undefined","responseData","data","status","Array","isArray","includes","matchesAnyTrigger","triggers","some","createQueuePlugin","config","onTrigger","enabled","checkOnError","queueTimeout","length","name","priority","HIGH","onBeforeRequest","onAfterRequest","onError","queueManager","matched","pause","timeoutId","setTimeout","clear","processAndResume","error","Error","message","String","clearTimeout"],"mappings":"AAAA;;;;;CAKC,GAID,SAASA,cAAc,QAAQ,kBAAkB;AACjD,SAASC,sBAAsB,QAAQ,+BAA+B;AAiEtE;;CAEC,GACD,SAASC,eACPC,QAAkB,EAClBC,OAAuB,EACvBC,OAA8B;IAE9B,YAAY;IACZ,IAAIA,QAAQC,KAAK,EAAE;QACjB,OAAOD,QAAQC,KAAK,CAACH,UAAUC;IACjC;IAEA,mBAAmB;IACnB,IAAIC,QAAQE,IAAI,KAAKC,WAAW;QAC9B,MAAMC,eAAeN,SAASO,IAAI;QAClC,IAAID,gBAAgB,OAAOA,iBAAiB,YAAY,UAAUA,cAAc;YAC9E,MAAMF,OAAO,AAACE,aAAmCF,IAAI;YAErD,IAAI,OAAOF,QAAQE,IAAI,KAAK,YAAY;gBACtC,IAAIF,QAAQE,IAAI,CAACA,OAAO;oBACtB,OAAO;gBACT;YACF,OAAO,IAAIA,SAASF,QAAQE,IAAI,EAAE;gBAChC,OAAO;YACT;QACF;IACF;IAEA,cAAc;IACd,IAAIF,QAAQM,MAAM,KAAKH,WAAW;QAChC,MAAMG,SAASR,SAASQ,MAAM;QAE9B,IAAI,OAAON,QAAQM,MAAM,KAAK,YAAY;YACxC,IAAIN,QAAQM,MAAM,CAACA,SAAS;gBAC1B,OAAO;YACT;QACF,OAAO,IAAIC,MAAMC,OAAO,CAACR,QAAQM,MAAM,GAAG;YACxC,IAAIN,QAAQM,MAAM,CAACG,QAAQ,CAACH,SAAS;gBACnC,OAAO;YACT;QACF,OAAO,IAAIA,WAAWN,QAAQM,MAAM,EAAE;YACpC,OAAO;QACT;IACF;IAEA,OAAO;AACT;AAEA;;CAEC,GACD,SAASI,kBACPZ,QAAkB,EAClBC,OAAuB,EACvBY,QAAiC;IAEjC,OAAOA,SAASC,IAAI,CAAC,CAACZ,UAAYH,eAAeC,UAAUC,SAASC;AACtE;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GACD,OAAO,SAASa,kBAAkBC,SAA4B,CAAC,CAAC;IAC9D,MAAM,EACJH,WAAW,EAAE,EACbI,SAAS,EACTC,UAAU,IAAI,EACdC,eAAe,IAAI,EACnBC,eAAe,KAAK,EACrB,GAAGJ;IAEJ,IAAIH,SAASQ,MAAM,KAAK,GAAG;QACzB,qBAAqB;QACrB,OAAO;YACLC,MAAM;YACNC,UAAU1B,eAAe2B,IAAI;YAC7BC,iBAAiBpB;YACjBqB,gBAAgBrB;YAChBsB,SAAStB;QACX;IACF;IAEA,MAAMuB,eAAe9B;IAErB,OAAO;QACLwB,MAAM;QACNC,UAAU1B,eAAe2B,IAAI;QAE7B,MAAME,gBAAezB,OAAuB;YAC1C,IAAI,CAACiB,WAAW,CAACjB,QAAQD,QAAQ,EAAE;gBACjC;YACF;YAEA,aAAa;YACb,MAAM6B,UAAUjB,kBAAkBX,QAAQD,QAAQ,EAAEC,SAASY;YAE7D,IAAIgB,WAAWZ,WAAW;gBACxB,oCAAoC;gBACpCW,aAAaE,KAAK;gBAElB,SAAS;gBACT,MAAMC,YAAYC,WAAW;oBAC3BJ,aAAaK,KAAK,CAAC;gBACrB,GAAGb;gBAEH,IAAI;oBACF,mBAAmB;oBACnB,MAAMQ,aAAaM,gBAAgB,CAAC;wBAClC,MAAMjB,UAAUhB,QAAQD,QAAQ,EAAGC;oBACrC;gBACF,EAAE,OAAOkC,OAAO;oBACd,cAAc;oBACdP,aAAaK,KAAK,CAAC,CAAC,QAAQ,EAAEE,iBAAiBC,QAAQD,MAAME,OAAO,GAAGC,OAAOH,QAAQ;gBACtF,yBAAyB;gBAC3B,SAAU;oBACRI,aAAaR;gBACf;YACF;QACF;QAEA,MAAMJ,SAAQ1B,OAAuB;YACnC,IAAI,CAACiB,WAAW,CAACC,gBAAgB,CAAClB,QAAQkC,KAAK,EAAE;gBAC/C;YACF;YAEA,mBAAmB;YACnB,IAAIlC,QAAQD,QAAQ,EAAE;gBACpB,MAAM6B,UAAUjB,kBAAkBX,QAAQD,QAAQ,EAAEC,SAASY;gBAE7D,IAAIgB,WAAWZ,WAAW;oBACxB,oCAAoC;oBACpCW,aAAaE,KAAK;oBAElB,SAAS;oBACT,MAAMC,YAAYC,WAAW;wBAC3BJ,aAAaK,KAAK,CAAC;oBACrB,GAAGb;oBAEH,IAAI;wBACF,mBAAmB;wBACnB,MAAMQ,aAAaM,gBAAgB,CAAC;4BAClC,MAAMjB,UAAUhB,QAAQD,QAAQ,EAAGC;wBACrC;wBAEA,wBAAwB;wBACxB,OAAO;oBACT,EAAE,OAAOkC,OAAO;wBACd,cAAc;wBACdP,aAAaK,KAAK,CAAC,CAAC,QAAQ,EAAEE,iBAAiBC,QAAQD,MAAME,OAAO,GAAGC,OAAOH,QAAQ;wBACtF,aAAa;wBACb,OAAO;oBACT,SAAU;wBACRI,aAAaR;oBACf;gBACF;YACF;YAEA,OAAO;QACT;IACF;AACF"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求队列管理器
|
|
3
|
+
*
|
|
4
|
+
* 用于管理待执行的请求队列,支持请求拦截、暂停、恢复等功能
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(exports, "getRequestQueueManager", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function() {
|
|
12
|
+
return getRequestQueueManager;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
function _define_property(obj, key, value) {
|
|
16
|
+
if (key in obj) {
|
|
17
|
+
Object.defineProperty(obj, key, {
|
|
18
|
+
value: value,
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
writable: true
|
|
22
|
+
});
|
|
23
|
+
} else {
|
|
24
|
+
obj[key] = value;
|
|
25
|
+
}
|
|
26
|
+
return obj;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 队列状态
|
|
30
|
+
*/ var QueueState = /*#__PURE__*/ function(QueueState) {
|
|
31
|
+
/**
|
|
32
|
+
* 正常状态,请求可以正常执行
|
|
33
|
+
*/ QueueState["IDLE"] = "idle";
|
|
34
|
+
/**
|
|
35
|
+
* 暂停状态,请求会被放入队列
|
|
36
|
+
*/ QueueState["PAUSED"] = "paused";
|
|
37
|
+
/**
|
|
38
|
+
* 处理中状态,正在执行回调
|
|
39
|
+
*/ QueueState["PROCESSING"] = "processing";
|
|
40
|
+
return QueueState;
|
|
41
|
+
}(QueueState || {});
|
|
42
|
+
/**
|
|
43
|
+
* 请求队列管理器
|
|
44
|
+
*/ let RequestQueueManager = class RequestQueueManager {
|
|
45
|
+
/**
|
|
46
|
+
* 获取单例实例
|
|
47
|
+
*/ static getInstance() {
|
|
48
|
+
if (!RequestQueueManager.instance) {
|
|
49
|
+
RequestQueueManager.instance = new RequestQueueManager();
|
|
50
|
+
}
|
|
51
|
+
return RequestQueueManager.instance;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 检查队列是否处于暂停状态
|
|
55
|
+
*/ isPaused() {
|
|
56
|
+
return this.state === "paused" || this.state === "processing";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 暂停队列,后续请求会被放入队列
|
|
60
|
+
*/ pause() {
|
|
61
|
+
if (this.state === "idle") {
|
|
62
|
+
this.state = "paused";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 恢复队列,执行队列中的所有请求
|
|
67
|
+
*/ async resume() {
|
|
68
|
+
if (this.state === "idle") {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.state = "idle";
|
|
72
|
+
// 执行队列中的所有请求
|
|
73
|
+
const requests = [
|
|
74
|
+
...this.queue
|
|
75
|
+
];
|
|
76
|
+
this.queue = [];
|
|
77
|
+
// 并行执行所有请求
|
|
78
|
+
await Promise.allSettled(requests.map((item)=>{
|
|
79
|
+
return item.execute().then(item.resolve).catch(item.reject);
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 执行回调并等待完成,然后恢复队列
|
|
84
|
+
*
|
|
85
|
+
* @param callback - 回调函数
|
|
86
|
+
*/ async processAndResume(callback) {
|
|
87
|
+
if (this.state === "processing") {
|
|
88
|
+
// 如果已经在处理中,等待当前处理完成
|
|
89
|
+
if (this.processingPromise) {
|
|
90
|
+
await this.processingPromise;
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.state = "processing";
|
|
95
|
+
try {
|
|
96
|
+
// 执行回调
|
|
97
|
+
this.processingPromise = Promise.resolve(callback());
|
|
98
|
+
await this.processingPromise;
|
|
99
|
+
} finally{
|
|
100
|
+
this.processingPromise = null;
|
|
101
|
+
// 恢复队列
|
|
102
|
+
await this.resume();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 将请求添加到队列
|
|
107
|
+
*
|
|
108
|
+
* @template TResponse - 响应数据类型
|
|
109
|
+
* @param options - 请求配置
|
|
110
|
+
* @param execute - 请求执行函数
|
|
111
|
+
* @returns Promise 解析为响应对象
|
|
112
|
+
*/ async enqueue(options, execute) {
|
|
113
|
+
return new Promise((resolve, reject)=>{
|
|
114
|
+
this.queue.push({
|
|
115
|
+
options: options,
|
|
116
|
+
execute: execute,
|
|
117
|
+
resolve: resolve,
|
|
118
|
+
reject,
|
|
119
|
+
timestamp: Date.now()
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 获取队列长度
|
|
125
|
+
*/ getQueueLength() {
|
|
126
|
+
return this.queue.length;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 清空队列
|
|
130
|
+
*
|
|
131
|
+
* @param reason - 清空原因(用于错误信息)
|
|
132
|
+
*/ clear(reason) {
|
|
133
|
+
const requests = [
|
|
134
|
+
...this.queue
|
|
135
|
+
];
|
|
136
|
+
this.queue = [];
|
|
137
|
+
// 拒绝所有待执行的请求
|
|
138
|
+
const error = reason ? new Error(`请求队列已清空: ${reason}`) : new Error('请求队列已清空');
|
|
139
|
+
requests.forEach((item)=>{
|
|
140
|
+
item.reject(error);
|
|
141
|
+
});
|
|
142
|
+
this.state = "idle";
|
|
143
|
+
this.processingPromise = null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 销毁管理器
|
|
147
|
+
*/ destroy() {
|
|
148
|
+
this.clear('管理器已销毁');
|
|
149
|
+
RequestQueueManager.instance = null;
|
|
150
|
+
}
|
|
151
|
+
constructor(){
|
|
152
|
+
/**
|
|
153
|
+
* 待执行的请求队列
|
|
154
|
+
*/ _define_property(this, "queue", []);
|
|
155
|
+
/**
|
|
156
|
+
* 队列状态
|
|
157
|
+
*/ _define_property(this, "state", "idle");
|
|
158
|
+
/**
|
|
159
|
+
* 当前正在执行的回调 Promise
|
|
160
|
+
*/ _define_property(this, "processingPromise", null);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
_define_property(RequestQueueManager, "instance", null);
|
|
164
|
+
function getRequestQueueManager() {
|
|
165
|
+
return RequestQueueManager.getInstance();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=RequestQueueManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/request/utils/RequestQueueManager.ts"],"sourcesContent":["/**\n * 请求队列管理器\n * \n * 用于管理待执行的请求队列,支持请求拦截、暂停、恢复等功能\n */\n\nimport type { RequestOptions, Response } from '../types';\n\n/**\n * 待执行的请求项\n */\ninterface QueuedRequest {\n /**\n * 请求配置\n */\n options: RequestOptions<unknown>;\n \n /**\n * 请求执行函数\n */\n execute: () => Promise<Response<unknown>>;\n \n /**\n * Promise resolve 函数\n */\n resolve: (response: Response<unknown>) => void;\n \n /**\n * Promise reject 函数\n */\n reject: (error: unknown) => void;\n \n /**\n * 请求时间戳\n */\n timestamp: number;\n}\n\n/**\n * 队列状态\n */\nenum QueueState {\n /**\n * 正常状态,请求可以正常执行\n */\n IDLE = 'idle',\n \n /**\n * 暂停状态,请求会被放入队列\n */\n PAUSED = 'paused',\n \n /**\n * 处理中状态,正在执行回调\n */\n PROCESSING = 'processing',\n}\n\n/**\n * 请求队列管理器\n */\nclass RequestQueueManager {\n private static instance: RequestQueueManager | null = null;\n \n /**\n * 待执行的请求队列\n */\n private queue: QueuedRequest[] = [];\n \n /**\n * 队列状态\n */\n private state: QueueState = QueueState.IDLE;\n \n /**\n * 当前正在执行的回调 Promise\n */\n private processingPromise: Promise<void> | null = null;\n \n /**\n * 获取单例实例\n */\n static getInstance(): RequestQueueManager {\n if (!RequestQueueManager.instance) {\n RequestQueueManager.instance = new RequestQueueManager();\n }\n return RequestQueueManager.instance;\n }\n \n /**\n * 检查队列是否处于暂停状态\n */\n isPaused(): boolean {\n return this.state === QueueState.PAUSED || this.state === QueueState.PROCESSING;\n }\n \n /**\n * 暂停队列,后续请求会被放入队列\n */\n pause(): void {\n if (this.state === QueueState.IDLE) {\n this.state = QueueState.PAUSED;\n }\n }\n \n /**\n * 恢复队列,执行队列中的所有请求\n */\n async resume(): Promise<void> {\n if (this.state === QueueState.IDLE) {\n return;\n }\n \n this.state = QueueState.IDLE;\n \n // 执行队列中的所有请求\n const requests = [...this.queue];\n this.queue = [];\n \n // 并行执行所有请求\n await Promise.allSettled(\n requests.map((item) => {\n return item\n .execute()\n .then(item.resolve)\n .catch(item.reject);\n })\n );\n }\n \n /**\n * 执行回调并等待完成,然后恢复队列\n * \n * @param callback - 回调函数\n */\n async processAndResume(callback: () => Promise<void> | void): Promise<void> {\n if (this.state === QueueState.PROCESSING) {\n // 如果已经在处理中,等待当前处理完成\n if (this.processingPromise) {\n await this.processingPromise;\n }\n return;\n }\n \n this.state = QueueState.PROCESSING;\n \n try {\n // 执行回调\n this.processingPromise = Promise.resolve(callback());\n await this.processingPromise;\n } finally {\n this.processingPromise = null;\n // 恢复队列\n await this.resume();\n }\n }\n \n /**\n * 将请求添加到队列\n * \n * @template TResponse - 响应数据类型\n * @param options - 请求配置\n * @param execute - 请求执行函数\n * @returns Promise 解析为响应对象\n */\n async enqueue<TResponse = unknown>(\n options: RequestOptions<TResponse>,\n execute: () => Promise<Response<TResponse>>\n ): Promise<Response<TResponse>> {\n return new Promise<Response<TResponse>>((resolve, reject) => {\n this.queue.push({\n options: options as RequestOptions<unknown>,\n execute: execute as () => Promise<Response<unknown>>,\n resolve: resolve as (response: Response<unknown>) => void,\n reject,\n timestamp: Date.now(),\n });\n });\n }\n \n /**\n * 获取队列长度\n */\n getQueueLength(): number {\n return this.queue.length;\n }\n \n /**\n * 清空队列\n * \n * @param reason - 清空原因(用于错误信息)\n */\n clear(reason?: string): void {\n const requests = [...this.queue];\n this.queue = [];\n \n // 拒绝所有待执行的请求\n const error = reason\n ? new Error(`请求队列已清空: ${reason}`)\n : new Error('请求队列已清空');\n \n requests.forEach((item) => {\n item.reject(error);\n });\n \n this.state = QueueState.IDLE;\n this.processingPromise = null;\n }\n \n /**\n * 销毁管理器\n */\n destroy(): void {\n this.clear('管理器已销毁');\n RequestQueueManager.instance = null;\n }\n}\n\n/**\n * 获取请求队列管理器实例\n */\nexport function getRequestQueueManager(): RequestQueueManager {\n return RequestQueueManager.getInstance();\n}\n"],"names":["getRequestQueueManager","QueueState","RequestQueueManager","getInstance","instance","isPaused","state","pause","resume","requests","queue","Promise","allSettled","map","item","execute","then","resolve","catch","reject","processAndResume","callback","processingPromise","enqueue","options","push","timestamp","Date","now","getQueueLength","length","clear","reason","error","Error","forEach","destroy"],"mappings":"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"}
|