@taicode/common-base 1.7.2 → 1.7.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/output/events/disposer.d.ts +6 -0
- package/output/events/disposer.d.ts.map +1 -0
- package/output/events/disposer.js +19 -0
- package/output/events/disposer.test.d.ts +2 -0
- package/output/events/disposer.test.d.ts.map +1 -0
- package/output/events/disposer.test.js +192 -0
- package/output/events/event-emitter.d.ts +33 -0
- package/output/events/event-emitter.d.ts.map +1 -0
- package/output/events/event-emitter.js +66 -0
- package/output/events/event-emitter.test.d.ts +2 -0
- package/output/events/event-emitter.test.d.ts.map +1 -0
- package/output/events/event-emitter.test.js +213 -0
- package/output/events/index.d.ts +3 -0
- package/output/events/index.d.ts.map +1 -0
- package/output/events/index.js +3 -0
- package/output/flow-queue/flow-queue.d.ts +418 -0
- package/output/flow-queue/flow-queue.d.ts.map +1 -0
- package/output/flow-queue/flow-queue.js +582 -0
- package/output/flow-queue/flow-queue.test.d.ts +2 -0
- package/output/flow-queue/flow-queue.test.d.ts.map +1 -0
- package/output/flow-queue/flow-queue.test.js +1033 -0
- package/output/flow-queue/index.d.ts +3 -0
- package/output/flow-queue/index.d.ts.map +1 -0
- package/output/flow-queue/index.js +3 -0
- package/output/index.d.ts +1 -0
- package/output/index.d.ts.map +1 -1
- package/output/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowQueue 流式队列处理器
|
|
3
|
+
*
|
|
4
|
+
* 一个支持异步处理、错误重试、延迟执行的队列管理工具,特别适用于以下场景:
|
|
5
|
+
*
|
|
6
|
+
* ## 使用场景
|
|
7
|
+
*
|
|
8
|
+
* ### 1. 复杂嵌套的解耦
|
|
9
|
+
* 当需要处理复杂的嵌套数据结构或多层依赖关系时,FlowQueue 可以帮助解耦处理逻辑:
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // 示例:处理嵌套的文件目录结构
|
|
13
|
+
* interface FileNode {
|
|
14
|
+
* path: string;
|
|
15
|
+
* children?: FileNode[];
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* const queue = new FlowQueue<FileNode, ProcessedFile>(
|
|
19
|
+
* { delay: { base: 100 } }, // 避免过快处理导致资源竞争
|
|
20
|
+
* async (node) => {
|
|
21
|
+
* // 处理当前文件节点
|
|
22
|
+
* const result = await processFile(node.path);
|
|
23
|
+
*
|
|
24
|
+
* // 将子节点加入队列,实现解耦
|
|
25
|
+
* if (node.children) {
|
|
26
|
+
* queue.input(...node.children);
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* return result;
|
|
30
|
+
* }
|
|
31
|
+
* );
|
|
32
|
+
*
|
|
33
|
+
* // 开始处理根节点
|
|
34
|
+
* queue.input(rootNode);
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ### 2. 深度循环的解耦
|
|
38
|
+
* 在需要处理大量数据或深度递归时,FlowQueue 可以避免调用栈溢出和阻塞主线程:
|
|
39
|
+
*
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // 示例:大数据集的分批处理
|
|
42
|
+
* interface DataChunk {
|
|
43
|
+
* data: any[];
|
|
44
|
+
* offset: number;
|
|
45
|
+
* total: number;
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* const queue = new FlowQueue<DataChunk, ProcessedChunk>(
|
|
49
|
+
* {
|
|
50
|
+
* delay: { base: 50, jitter: true }, // 避免连续处理导致的性能问题
|
|
51
|
+
* retry: { strategy: 'exponential', baseDelay: 1000, maxRetries: 3 }
|
|
52
|
+
* },
|
|
53
|
+
* async (chunk) => {
|
|
54
|
+
* const result = await processDataChunk(chunk.data);
|
|
55
|
+
*
|
|
56
|
+
* // 如果还有更多数据,继续加入队列
|
|
57
|
+
* if (chunk.offset + chunk.data.length < chunk.total) {
|
|
58
|
+
* const nextChunk = await loadNextChunk(chunk.offset + chunk.data.length);
|
|
59
|
+
* queue.input(nextChunk);
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* return result;
|
|
63
|
+
* }
|
|
64
|
+
* );
|
|
65
|
+
*
|
|
66
|
+
* // 监听处理进度
|
|
67
|
+
* queue.onGenerate((result) => {
|
|
68
|
+
* updateProgress(result);
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* ## 关键特性
|
|
73
|
+
* - **异步解耦**:通过队列机制避免深度递归和调用栈溢出
|
|
74
|
+
* - **错误重试**:支持多种重试策略,提高处理的可靠性
|
|
75
|
+
* - **流量控制**:可配置任务间延迟,避免资源竞争
|
|
76
|
+
* - **状态追踪**:清晰的成功/失败状态管理
|
|
77
|
+
* - **背压处理**:避免内存溢出的队列管理
|
|
78
|
+
*/
|
|
79
|
+
import { catchIt } from '../catch';
|
|
80
|
+
function delay(arg0) {
|
|
81
|
+
return new Promise(resolve => setTimeout(() => resolve(), arg0));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* FlowQueue 流式队列处理器类
|
|
85
|
+
*
|
|
86
|
+
* 提供高效的异步队列处理能力,特别适用于需要解耦复杂嵌套结构和深度循环的场景。
|
|
87
|
+
*
|
|
88
|
+
* @template I 输入数据类型,继承自 FlowQueueInput
|
|
89
|
+
* @template O 输出数据类型,继承自 FlowQueueOutput
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* // 创建队列实例
|
|
94
|
+
* const queue = new FlowQueue<InputType, OutputType>(
|
|
95
|
+
* {
|
|
96
|
+
* retry: { strategy: 'exponential', baseDelay: 1000, maxRetries: 3 },
|
|
97
|
+
* delay: { base: 100, jitter: true }
|
|
98
|
+
* },
|
|
99
|
+
* async (input) => {
|
|
100
|
+
* // 处理逻辑
|
|
101
|
+
* return await processInput(input);
|
|
102
|
+
* }
|
|
103
|
+
* );
|
|
104
|
+
*
|
|
105
|
+
* // 添加输入并监听输出
|
|
106
|
+
* queue.input(data1, data2, data3);
|
|
107
|
+
* queue.onGenerate(result => console.log('处理完成:', result));
|
|
108
|
+
*
|
|
109
|
+
* // 等待所有处理完成
|
|
110
|
+
* queue.closeInput();
|
|
111
|
+
* await queue.awaitCompletion();
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export class FlowQueue {
|
|
115
|
+
options;
|
|
116
|
+
processor;
|
|
117
|
+
pendingInputs = []; // 待处理
|
|
118
|
+
processedInputs = []; // 已处理
|
|
119
|
+
successOutputs = new Map();
|
|
120
|
+
erroredOutputs = new Map();
|
|
121
|
+
isProcessing = false; // 防止并发处理
|
|
122
|
+
retryCount = new Map(); // 记录每个输入的重试次数
|
|
123
|
+
onGenerates = new Map();
|
|
124
|
+
isInputClosed = false; // 标记输入是否已关闭
|
|
125
|
+
completionPromise = null; // 完成状态的 Promise
|
|
126
|
+
completionResolve = null; // 完成状态的 resolve 函数
|
|
127
|
+
/**
|
|
128
|
+
* 创建 FlowQueue 实例
|
|
129
|
+
*
|
|
130
|
+
* @param options 队列配置选项
|
|
131
|
+
* @param processor 处理函数,接收输入并返回处理结果的 Promise
|
|
132
|
+
*
|
|
133
|
+
* processor 函数是解耦的核心:
|
|
134
|
+
* - 可以在处理过程中通过 this.input() 动态添加新任务
|
|
135
|
+
* - 支持异步处理,避免阻塞主线程
|
|
136
|
+
* - 失败时会根据重试策略自动重试
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* // 处理嵌套文件结构的示例
|
|
141
|
+
* const fileQueue = new FlowQueue<FileNode, ProcessedFile>(
|
|
142
|
+
* {
|
|
143
|
+
* retry: { strategy: 'exponential', baseDelay: 1000, maxRetries: 3 },
|
|
144
|
+
* delay: { base: 100, jitter: true }
|
|
145
|
+
* },
|
|
146
|
+
* async (node) => {
|
|
147
|
+
* try {
|
|
148
|
+
* const result = await processFile(node.path);
|
|
149
|
+
*
|
|
150
|
+
* // 动态添加子节点,实现深度优先遍历的解耦
|
|
151
|
+
* if (node.children) {
|
|
152
|
+
* fileQueue.input(...node.children);
|
|
153
|
+
* }
|
|
154
|
+
*
|
|
155
|
+
* return result;
|
|
156
|
+
* } catch (error) {
|
|
157
|
+
* throw new Error(`处理文件 ${node.path} 失败: ${error.message}`);
|
|
158
|
+
* }
|
|
159
|
+
* }
|
|
160
|
+
* );
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
constructor(options, processor) {
|
|
164
|
+
this.options = options;
|
|
165
|
+
this.processor = processor;
|
|
166
|
+
// 设置重试配置默认值
|
|
167
|
+
this.options.retry = this.options.retry || { strategy: 'immediate' };
|
|
168
|
+
this.options.retry.maxRetries = this.options.retry.maxRetries ?? 3;
|
|
169
|
+
this.options.retry.jitter = this.options.retry.jitter ?? false;
|
|
170
|
+
// 设置任务间延迟配置默认值
|
|
171
|
+
this.options.delay = this.options.delay || {};
|
|
172
|
+
this.options.delay.base = this.options.delay.base ?? 0;
|
|
173
|
+
this.options.delay.jitter = this.options.delay.jitter ?? false;
|
|
174
|
+
this.options.delay.jitterRange = this.options.delay.jitterRange ?? 0.1;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 计算重试延迟时间
|
|
178
|
+
*/
|
|
179
|
+
calculateRetryDelay(retryCount) {
|
|
180
|
+
const retryConfig = this.options.retry;
|
|
181
|
+
let delay = 0;
|
|
182
|
+
switch (retryConfig.strategy) {
|
|
183
|
+
case 'immediate':
|
|
184
|
+
case undefined:
|
|
185
|
+
delay = 0;
|
|
186
|
+
break;
|
|
187
|
+
case 'fixed':
|
|
188
|
+
delay = retryConfig.baseDelay;
|
|
189
|
+
if (retryConfig.maxDelay) {
|
|
190
|
+
delay = Math.min(delay, retryConfig.maxDelay);
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case 'exponential':
|
|
194
|
+
const multiplier = retryConfig.multiplier ?? 2;
|
|
195
|
+
delay = retryConfig.baseDelay * Math.pow(multiplier, retryCount);
|
|
196
|
+
if (retryConfig.maxDelay) {
|
|
197
|
+
delay = Math.min(delay, retryConfig.maxDelay);
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
case 'linear':
|
|
201
|
+
delay = retryConfig.baseDelay * (retryCount + 1);
|
|
202
|
+
if (retryConfig.maxDelay) {
|
|
203
|
+
delay = Math.min(delay, retryConfig.maxDelay);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case 'custom':
|
|
207
|
+
const baseDelay = retryConfig.baseDelay ?? 1000;
|
|
208
|
+
delay = retryConfig.customDelayFn(retryCount, baseDelay);
|
|
209
|
+
if (retryConfig.maxDelay) {
|
|
210
|
+
delay = Math.min(delay, retryConfig.maxDelay);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
delay = 0;
|
|
215
|
+
}
|
|
216
|
+
// 添加随机抖动
|
|
217
|
+
if (retryConfig.jitter && delay > 0) {
|
|
218
|
+
const jitterRange = delay * 0.1; // 10% 的抖动范围
|
|
219
|
+
const jitter = (Math.random() - 0.5) * 2 * jitterRange;
|
|
220
|
+
delay = Math.max(0, delay + jitter);
|
|
221
|
+
}
|
|
222
|
+
return Math.floor(delay);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 计算任务间延迟时间
|
|
226
|
+
*/
|
|
227
|
+
calculateTaskDelay() {
|
|
228
|
+
const delayConfig = this.options.delay;
|
|
229
|
+
let delay = delayConfig.base;
|
|
230
|
+
if (delayConfig.customDelayFn) {
|
|
231
|
+
delay = delayConfig.customDelayFn();
|
|
232
|
+
}
|
|
233
|
+
// 添加随机抖动
|
|
234
|
+
if (delayConfig.jitter && delay > 0) {
|
|
235
|
+
const jitterRange = delay * delayConfig.jitterRange;
|
|
236
|
+
const jitter = (Math.random() - 0.5) * 2 * jitterRange;
|
|
237
|
+
delay = Math.max(0, delay + jitter);
|
|
238
|
+
}
|
|
239
|
+
return Math.floor(delay);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 调度下一个处理任务
|
|
243
|
+
*/
|
|
244
|
+
scheduleNextProcessing() {
|
|
245
|
+
if (this.pendingInputs.length > 0) {
|
|
246
|
+
// 计算任务间延迟
|
|
247
|
+
const taskDelay = this.calculateTaskDelay();
|
|
248
|
+
if (taskDelay > 0) {
|
|
249
|
+
// 有延迟,使用 setTimeout
|
|
250
|
+
setTimeout(() => this.processFlow(), taskDelay);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// 无延迟,使用 setTimeout(0) 避免调用栈过深
|
|
254
|
+
setTimeout(() => this.processFlow(), 0);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// 没有更多待处理的输入,检查是否已标记为完成
|
|
259
|
+
this.checkAndTriggerCompletion();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* 延迟执行重试
|
|
264
|
+
*/
|
|
265
|
+
scheduleRetry(input, delayTime) {
|
|
266
|
+
this.pendingInputs.unshift(input);
|
|
267
|
+
if (delayTime <= 0) {
|
|
268
|
+
// 立即重试
|
|
269
|
+
setTimeout(() => this.processFlow(), 0);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// 延迟重试
|
|
273
|
+
setTimeout(() => this.processFlow(), delayTime);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* 检查是否已完成并触发完成回调
|
|
278
|
+
*/
|
|
279
|
+
async checkAndTriggerCompletion() {
|
|
280
|
+
await delay(0); // 确保在当前执行栈完成后再检查
|
|
281
|
+
if (this.isInputClosed && this.pendingInputs.length === 0 && !this.isProcessing && this.completionResolve) {
|
|
282
|
+
this.completionResolve();
|
|
283
|
+
this.completionResolve = null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* 检查当前的待处理队列,取出一个进行处理
|
|
288
|
+
* 处理完成后更新 output 并将输入移动到 processedInputs
|
|
289
|
+
* 当 processor 处理失败时,将错误信息存入 output(FlowError)
|
|
290
|
+
* 可以设置失败的重试次数,如果超过最大重试次数则不再重试
|
|
291
|
+
*/
|
|
292
|
+
async processFlow() {
|
|
293
|
+
// 如果正在处理或没有待处理的输入,直接返回
|
|
294
|
+
if (this.isProcessing || this.pendingInputs.length === 0) {
|
|
295
|
+
// 检查是否已标记为完成且没有待处理的输入
|
|
296
|
+
this.checkAndTriggerCompletion();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this.isProcessing = true;
|
|
300
|
+
const processResult = await catchIt(async () => {
|
|
301
|
+
// 取出第一个待处理的输入
|
|
302
|
+
const input = this.pendingInputs.shift();
|
|
303
|
+
if (!input) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// 获取当前输入的重试次数
|
|
307
|
+
const currentRetries = this.retryCount.get(input) || 0;
|
|
308
|
+
// 使用 processor 处理输入
|
|
309
|
+
const result = await catchIt(() => this.processor(input));
|
|
310
|
+
if (result.isError()) {
|
|
311
|
+
if (currentRetries < this.options.retry.maxRetries) {
|
|
312
|
+
// 还可以重试,增加重试次数并计算延迟
|
|
313
|
+
this.retryCount.set(input, currentRetries + 1);
|
|
314
|
+
const delay = this.calculateRetryDelay(currentRetries);
|
|
315
|
+
this.scheduleRetry(input, delay);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// 超过最大重试次数,记录错误并移动到已处理
|
|
319
|
+
this.erroredOutputs.set(input, result.error);
|
|
320
|
+
this.processedInputs.push(input);
|
|
321
|
+
this.retryCount.delete(input);
|
|
322
|
+
// 处理完当前输入后,继续处理下一个(如果有的话)
|
|
323
|
+
this.scheduleNextProcessing();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
// 处理成功,存储结果
|
|
328
|
+
this.successOutputs.set(input, result.value);
|
|
329
|
+
this.processedInputs.push(input);
|
|
330
|
+
this.retryCount.delete(input);
|
|
331
|
+
// 清除可能存在的错误记录
|
|
332
|
+
this.erroredOutputs.delete(input);
|
|
333
|
+
// 通知所有注册的回调
|
|
334
|
+
await this.notifyOnGenerates(result.value);
|
|
335
|
+
// 处理完当前输入后,继续处理下一个(如果有的话)
|
|
336
|
+
this.scheduleNextProcessing();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
// 处理 processFlow 中的错误
|
|
340
|
+
if (processResult.isError()) {
|
|
341
|
+
console.error('Error in processFlow:', processResult.error);
|
|
342
|
+
}
|
|
343
|
+
// 确保在任何情况下都重置 isProcessing 状态
|
|
344
|
+
this.isProcessing = false;
|
|
345
|
+
this.checkAndTriggerCompletion();
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* 通知所有注册的回调函数
|
|
349
|
+
*/
|
|
350
|
+
async notifyOnGenerates(output) {
|
|
351
|
+
for (const callback of this.onGenerates.values()) {
|
|
352
|
+
const result = await catchIt(() => callback(output));
|
|
353
|
+
if (result.isError()) {
|
|
354
|
+
console.error('Error in onGenerate callback:', result.error);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 向队列添加输入数据
|
|
360
|
+
*
|
|
361
|
+
* 此方法是解耦复杂嵌套结构的关键:
|
|
362
|
+
* - 在 processor 函数内部可以动态添加新的输入
|
|
363
|
+
* - 避免了深度递归调用栈
|
|
364
|
+
* - 支持批量添加多个输入
|
|
365
|
+
*
|
|
366
|
+
* @param inputs 要处理的输入数据
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```typescript
|
|
370
|
+
* // 在处理器中动态添加子任务
|
|
371
|
+
* const processor = async (node: TreeNode) => {
|
|
372
|
+
* const result = await processNode(node);
|
|
373
|
+
*
|
|
374
|
+
* // 将子节点加入队列,实现解耦
|
|
375
|
+
* if (node.children) {
|
|
376
|
+
* queue.input(...node.children);
|
|
377
|
+
* }
|
|
378
|
+
*
|
|
379
|
+
* return result;
|
|
380
|
+
* };
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
input(...inputs) {
|
|
384
|
+
if (this.isInputClosed)
|
|
385
|
+
return;
|
|
386
|
+
this.pendingInputs.push(...inputs);
|
|
387
|
+
this.processFlow();
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* 注册输出监听器
|
|
391
|
+
*
|
|
392
|
+
* 用于监听每个成功处理的输出,适用于:
|
|
393
|
+
* - 实时更新处理进度
|
|
394
|
+
* - 流式处理大数据集
|
|
395
|
+
* - 实现观察者模式解耦
|
|
396
|
+
*
|
|
397
|
+
* @param callback 当有新输出时调用的回调函数
|
|
398
|
+
* @returns 取消监听的函数
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```typescript
|
|
402
|
+
* // 监听处理进度
|
|
403
|
+
* const unsubscribe = queue.onGenerate((result) => {
|
|
404
|
+
* updateProgressBar(result);
|
|
405
|
+
* saveToDatabase(result);
|
|
406
|
+
* });
|
|
407
|
+
*
|
|
408
|
+
* // 在适当时机取消监听
|
|
409
|
+
* unsubscribe();
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
onGenerate(callback) {
|
|
413
|
+
const id = Math.random().toString(36);
|
|
414
|
+
this.onGenerates.set(id, callback);
|
|
415
|
+
return () => this.onGenerates.delete(id);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* 只读标记,代表是否当前已经 closeInput 并且所有输入都已经处理完毕
|
|
419
|
+
*/
|
|
420
|
+
get isCompleted() {
|
|
421
|
+
return this.isInputClosed && this.pendingInputs.length === 0 && !this.isProcessing;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* 标记当前 FlowQueue 不会再有新的输入了
|
|
425
|
+
*
|
|
426
|
+
* 调用此方法后:
|
|
427
|
+
* - 队列将不再接受新的输入
|
|
428
|
+
* - 当所有现有输入处理完成后,队列将进入完成状态
|
|
429
|
+
* - 可以通过 awaitCompletion() 等待所有处理完成
|
|
430
|
+
*
|
|
431
|
+
* 在深度循环解耦场景中,这是控制处理边界的重要方法。
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```typescript
|
|
435
|
+
* // 添加所有初始数据
|
|
436
|
+
* queue.input(...initialData);
|
|
437
|
+
*
|
|
438
|
+
* // 标记输入结束
|
|
439
|
+
* queue.closeInput();
|
|
440
|
+
*
|
|
441
|
+
* // 等待所有处理完成
|
|
442
|
+
* await queue.awaitCompletion();
|
|
443
|
+
* console.log('所有数据处理完成');
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
closeInput() {
|
|
447
|
+
this.isInputClosed = true;
|
|
448
|
+
// 使用统一的完成检查方法
|
|
449
|
+
this.checkAndTriggerCompletion();
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* 返回一个 Promise,代表当前已经标记为输入关闭状态、所有输入都已经处理完毕
|
|
453
|
+
*
|
|
454
|
+
* 这是等待所有异步处理完成的最佳方式,特别适用于:
|
|
455
|
+
* - 等待深度循环处理完成
|
|
456
|
+
* - 确保所有嵌套任务都已处理
|
|
457
|
+
* - 在测试中等待异步操作完成
|
|
458
|
+
*
|
|
459
|
+
* @returns Promise,在所有输入处理完成时 resolve
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```typescript
|
|
463
|
+
* // 处理大量嵌套数据
|
|
464
|
+
* queue.input(rootData);
|
|
465
|
+
* queue.closeInput();
|
|
466
|
+
*
|
|
467
|
+
* // 等待所有嵌套处理完成
|
|
468
|
+
* await queue.awaitCompletion();
|
|
469
|
+
*
|
|
470
|
+
* // 检查处理结果
|
|
471
|
+
* console.log('成功处理:', queue.successOutputs.size);
|
|
472
|
+
* console.log('处理失败:', queue.erroredOutputs.size);
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
awaitCompletion() {
|
|
476
|
+
if (this.completionPromise) {
|
|
477
|
+
return this.completionPromise;
|
|
478
|
+
}
|
|
479
|
+
// 如果已经标记为输入关闭且没有待处理的输入,立即返回 resolved Promise
|
|
480
|
+
if (this.isInputClosed && this.pendingInputs.length === 0 && !this.isProcessing) {
|
|
481
|
+
console.log('所有输入已处理完毕');
|
|
482
|
+
return Promise.resolve();
|
|
483
|
+
}
|
|
484
|
+
// 创建新的 Promise
|
|
485
|
+
this.completionPromise = new Promise((resolve) => {
|
|
486
|
+
this.completionResolve = () => {
|
|
487
|
+
console.log('所有输入已处理完毕');
|
|
488
|
+
resolve();
|
|
489
|
+
};
|
|
490
|
+
});
|
|
491
|
+
return this.completionPromise;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* 移除尚未处理的输入并调用 closeInput
|
|
495
|
+
*/
|
|
496
|
+
cancel() {
|
|
497
|
+
// 清除所有待处理的输入
|
|
498
|
+
this.pendingInputs.length = 0;
|
|
499
|
+
// 清除重试计数
|
|
500
|
+
this.retryCount.clear();
|
|
501
|
+
// 标记输入已关闭
|
|
502
|
+
this.closeInput();
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* 重置 FlowQueue 到初始状态
|
|
506
|
+
* 清空所有数据,包括待处理、已处理、输出、重试计数等
|
|
507
|
+
* 重置后可以重新开始处理
|
|
508
|
+
*/
|
|
509
|
+
reset() {
|
|
510
|
+
// 清空所有队列和状态
|
|
511
|
+
this.successOutputs.clear();
|
|
512
|
+
this.erroredOutputs.clear();
|
|
513
|
+
this.retryCount.clear();
|
|
514
|
+
this.pendingInputs.length = 0;
|
|
515
|
+
this.processedInputs.length = 0;
|
|
516
|
+
// 重置处理状态
|
|
517
|
+
this.isProcessing = false;
|
|
518
|
+
this.isInputClosed = false;
|
|
519
|
+
// 重置完成状态
|
|
520
|
+
if (this.completionResolve) {
|
|
521
|
+
this.completionResolve();
|
|
522
|
+
this.completionResolve = null;
|
|
523
|
+
}
|
|
524
|
+
this.completionPromise = null;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* 重试所有失败的输入
|
|
528
|
+
*
|
|
529
|
+
* 自动将所有失败的输入从已处理队列移回待处理队列,并启动处理。
|
|
530
|
+
* 在处理复杂嵌套结构时,某些节点可能因为依赖关系而暂时失败,
|
|
531
|
+
* 此方法可以在依赖解决后重新处理这些失败的节点。
|
|
532
|
+
*
|
|
533
|
+
* @returns 被移动到待处理队列的输入数量
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* // 第一轮处理
|
|
538
|
+
* queue.input(...allData);
|
|
539
|
+
* queue.closeInput();
|
|
540
|
+
* await queue.awaitCompletion();
|
|
541
|
+
*
|
|
542
|
+
* // 检查是否有失败的项
|
|
543
|
+
* if (queue.erroredOutputs.size > 0) {
|
|
544
|
+
* console.log(`有 ${queue.erroredOutputs.size} 个项目处理失败`);
|
|
545
|
+
*
|
|
546
|
+
* // 重试失败的项目
|
|
547
|
+
* const retryCount = queue.retry();
|
|
548
|
+
* console.log(`重试 ${retryCount} 个项目`);
|
|
549
|
+
*
|
|
550
|
+
* // 等待重试完成
|
|
551
|
+
* await queue.awaitCompletion();
|
|
552
|
+
* }
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
555
|
+
retry() {
|
|
556
|
+
// 直接从 erroredOutputs Map 中获取所有失败的输入
|
|
557
|
+
const retryInputs = Array.from(this.erroredOutputs.keys());
|
|
558
|
+
if (retryInputs.length > 0) {
|
|
559
|
+
// 从已处理队列中移除这些输入
|
|
560
|
+
for (const input of retryInputs) {
|
|
561
|
+
const index = this.processedInputs.indexOf(input);
|
|
562
|
+
if (index > -1) {
|
|
563
|
+
this.processedInputs.splice(index, 1);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// 清除错误记录和重试计数
|
|
567
|
+
this.erroredOutputs.clear();
|
|
568
|
+
for (const input of retryInputs) {
|
|
569
|
+
this.retryCount.delete(input);
|
|
570
|
+
}
|
|
571
|
+
// 将要重试的输入添加到待处理队列的前面(优先处理)
|
|
572
|
+
this.pendingInputs.unshift(...retryInputs);
|
|
573
|
+
// 启动处理(保持输入关闭状态,仅重试现有错误)
|
|
574
|
+
this.processFlow();
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
// 没有失败的输入需要重试,检查是否应该触发完成状态
|
|
578
|
+
this.checkAndTriggerCompletion();
|
|
579
|
+
}
|
|
580
|
+
return retryInputs.length;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-queue.test.d.ts","sourceRoot":"","sources":["../../source/flow-queue/flow-queue.test.ts"],"names":[],"mappings":""}
|