@taicode/common-base 3.1.0 → 3.1.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/output/disposer/disposer.d.ts +4 -2
- package/output/disposer/disposer.d.ts.map +1 -1
- package/output/disposer/disposer.js +5 -4
- package/output/disposer/disposer.test.js +72 -48
- 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.test.js +5 -4
- package/output/ring-cache/ring-cache.test.js +4 -3
- package/package.json +4 -4
- package/output/scheduler/index.d.ts +0 -2
- package/output/scheduler/index.d.ts.map +0 -1
- package/output/scheduler/index.js +0 -1
- package/output/scheduler/scheduler.d.ts +0 -222
- package/output/scheduler/scheduler.d.ts.map +0 -1
- package/output/scheduler/scheduler.js +0 -348
- package/output/scheduler/scheduler.test.d.ts +0 -2
- package/output/scheduler/scheduler.test.d.ts.map +0 -1
- package/output/scheduler/scheduler.test.js +0 -341
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from '../event-emitter';
|
|
2
|
-
import { catchIt } from '../catch';
|
|
3
|
-
/**
|
|
4
|
-
* 定时任务调度器
|
|
5
|
-
*
|
|
6
|
-
* 以 10 秒为基本时间单位,支持多个不同间隔的定时任务
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* const scheduler = new Scheduler()
|
|
11
|
-
*
|
|
12
|
-
* // 添加每 30 秒执行一次的任务
|
|
13
|
-
* scheduler.addTask({
|
|
14
|
-
* id: 'sync-data',
|
|
15
|
-
* interval: 30,
|
|
16
|
-
* handler: async () => {
|
|
17
|
-
* await syncDataToServer()
|
|
18
|
-
* },
|
|
19
|
-
* maxRetries: 3
|
|
20
|
-
* })
|
|
21
|
-
*
|
|
22
|
-
* // 添加每 60 秒执行一次的任务
|
|
23
|
-
* scheduler.addTask({
|
|
24
|
-
* id: 'cleanup',
|
|
25
|
-
* interval: 60,
|
|
26
|
-
* handler: async () => {
|
|
27
|
-
* await cleanupTempFiles()
|
|
28
|
-
* }
|
|
29
|
-
* })
|
|
30
|
-
*
|
|
31
|
-
* // 启动调度器
|
|
32
|
-
* scheduler.start()
|
|
33
|
-
*
|
|
34
|
-
* // 监听事件
|
|
35
|
-
* scheduler.on('task:error', (taskId, error) => {
|
|
36
|
-
* console.error(`Task ${taskId} failed:`, error)
|
|
37
|
-
* })
|
|
38
|
-
*
|
|
39
|
-
* // 停止调度器
|
|
40
|
-
* scheduler.stop()
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export class Scheduler extends EventEmitter {
|
|
44
|
-
/**
|
|
45
|
-
* 基本时间单位(秒)
|
|
46
|
-
*/
|
|
47
|
-
static TICK_INTERVAL = 10;
|
|
48
|
-
/**
|
|
49
|
-
* 当前 tick 计数
|
|
50
|
-
*/
|
|
51
|
-
currentTick = 0;
|
|
52
|
-
/**
|
|
53
|
-
* 定时器 ID
|
|
54
|
-
*/
|
|
55
|
-
timerId = null;
|
|
56
|
-
/**
|
|
57
|
-
* 是否正在运行
|
|
58
|
-
*/
|
|
59
|
-
running = false;
|
|
60
|
-
/**
|
|
61
|
-
* 任务状态映射
|
|
62
|
-
*/
|
|
63
|
-
tasks = new Map();
|
|
64
|
-
/**
|
|
65
|
-
* 添加定时任务
|
|
66
|
-
*/
|
|
67
|
-
addTask(config) {
|
|
68
|
-
// 验证参数
|
|
69
|
-
if (!config.id) {
|
|
70
|
-
throw new Error('[Scheduler] Task id is required');
|
|
71
|
-
}
|
|
72
|
-
if (!config.interval || config.interval <= 0) {
|
|
73
|
-
throw new Error('[Scheduler] Task interval must be positive');
|
|
74
|
-
}
|
|
75
|
-
if (config.interval % Scheduler.TICK_INTERVAL !== 0) {
|
|
76
|
-
throw new Error(`[Scheduler] Task interval must be a multiple of ${Scheduler.TICK_INTERVAL} seconds`);
|
|
77
|
-
}
|
|
78
|
-
if (!config.handler) {
|
|
79
|
-
throw new Error('[Scheduler] Task handler is required');
|
|
80
|
-
}
|
|
81
|
-
if (this.tasks.has(config.id)) {
|
|
82
|
-
throw new Error(`[Scheduler] Task with id "${config.id}" already exists`);
|
|
83
|
-
}
|
|
84
|
-
// 创建任务状态
|
|
85
|
-
const taskState = {
|
|
86
|
-
config: {
|
|
87
|
-
id: config.id,
|
|
88
|
-
interval: config.interval,
|
|
89
|
-
handler: config.handler,
|
|
90
|
-
maxRetries: config.maxRetries ?? 3,
|
|
91
|
-
retryDelay: config.retryDelay ?? 1000,
|
|
92
|
-
immediate: config.immediate ?? false,
|
|
93
|
-
enabled: config.enabled ?? true,
|
|
94
|
-
},
|
|
95
|
-
nextTick: config.immediate ? 0 : Math.ceil(config.interval / Scheduler.TICK_INTERVAL),
|
|
96
|
-
running: false,
|
|
97
|
-
retryCount: 0,
|
|
98
|
-
executions: [],
|
|
99
|
-
};
|
|
100
|
-
this.tasks.set(config.id, taskState);
|
|
101
|
-
console.log(`[Scheduler] Task "${config.id}" added (interval: ${config.interval}s)`);
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* 移除定时任务
|
|
105
|
-
*/
|
|
106
|
-
removeTask(taskId) {
|
|
107
|
-
const task = this.tasks.get(taskId);
|
|
108
|
-
if (!task) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
if (task.running) {
|
|
112
|
-
console.warn(`[Scheduler] Cannot remove running task "${taskId}"`);
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
this.tasks.delete(taskId);
|
|
116
|
-
console.log(`[Scheduler] Task "${taskId}" removed`);
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* 启用任务
|
|
121
|
-
*/
|
|
122
|
-
enableTask(taskId) {
|
|
123
|
-
const task = this.tasks.get(taskId);
|
|
124
|
-
if (!task) {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
task.config.enabled = true;
|
|
128
|
-
console.log(`[Scheduler] Task "${taskId}" enabled`);
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* 禁用任务
|
|
133
|
-
*/
|
|
134
|
-
disableTask(taskId) {
|
|
135
|
-
const task = this.tasks.get(taskId);
|
|
136
|
-
if (!task) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
task.config.enabled = false;
|
|
140
|
-
console.log(`[Scheduler] Task "${taskId}" disabled`);
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* 获取任务状态
|
|
145
|
-
*/
|
|
146
|
-
getTaskState(taskId) {
|
|
147
|
-
const task = this.tasks.get(taskId);
|
|
148
|
-
return task ? { ...task } : undefined;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* 获取所有任务 ID
|
|
152
|
-
*/
|
|
153
|
-
getTaskIds() {
|
|
154
|
-
return Array.from(this.tasks.keys());
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* 启动调度器
|
|
158
|
-
*/
|
|
159
|
-
start() {
|
|
160
|
-
if (this.running) {
|
|
161
|
-
console.warn('[Scheduler] Scheduler is already running');
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
this.running = true;
|
|
165
|
-
this.currentTick = 0;
|
|
166
|
-
this.emit('scheduler:start', undefined);
|
|
167
|
-
console.log('[Scheduler] Scheduler started');
|
|
168
|
-
// 立即执行一次 tick
|
|
169
|
-
this.tick();
|
|
170
|
-
// 设置定时器
|
|
171
|
-
this.timerId = setInterval(() => {
|
|
172
|
-
this.tick();
|
|
173
|
-
}, Scheduler.TICK_INTERVAL * 1000);
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* 停止调度器
|
|
177
|
-
*/
|
|
178
|
-
stop() {
|
|
179
|
-
if (!this.running) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (this.timerId) {
|
|
183
|
-
clearInterval(this.timerId);
|
|
184
|
-
this.timerId = null;
|
|
185
|
-
}
|
|
186
|
-
this.running = false;
|
|
187
|
-
this.emit('scheduler:stop', undefined);
|
|
188
|
-
console.log('[Scheduler] Scheduler stopped');
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* 是否正在运行
|
|
192
|
-
*/
|
|
193
|
-
isRunning() {
|
|
194
|
-
return this.running;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* 获取当前 tick
|
|
198
|
-
*/
|
|
199
|
-
getCurrentTick() {
|
|
200
|
-
return this.currentTick;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* 手动触发任务执行(忽略时间间隔)
|
|
204
|
-
*/
|
|
205
|
-
async triggerTask(taskId) {
|
|
206
|
-
const task = this.tasks.get(taskId);
|
|
207
|
-
if (!task) {
|
|
208
|
-
return catchIt(async () => {
|
|
209
|
-
throw new Error(`Task "${taskId}" not found`);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
return this.executeTask(task);
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* 执行 tick
|
|
216
|
-
*/
|
|
217
|
-
tick() {
|
|
218
|
-
this.currentTick++;
|
|
219
|
-
this.emit('scheduler:tick', { tick: this.currentTick });
|
|
220
|
-
// 检查需要执行的任务
|
|
221
|
-
for (const task of this.tasks.values()) {
|
|
222
|
-
if (!task.config.enabled) {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (task.running) {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
// 检查是否到达执行时间
|
|
229
|
-
if (this.currentTick >= task.nextTick) {
|
|
230
|
-
// 异步执行任务,不阻塞 tick
|
|
231
|
-
this.executeTask(task).catch((error) => {
|
|
232
|
-
console.error(`[Scheduler] Unexpected error in task "${task.config.id}":`, error);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* 执行任务
|
|
239
|
-
*/
|
|
240
|
-
async executeTask(task) {
|
|
241
|
-
const { config } = task;
|
|
242
|
-
const startTime = Date.now();
|
|
243
|
-
task.running = true;
|
|
244
|
-
this.emit('task:start', { taskId: config.id });
|
|
245
|
-
const execution = {
|
|
246
|
-
taskId: config.id,
|
|
247
|
-
startTime,
|
|
248
|
-
success: false,
|
|
249
|
-
retryCount: task.retryCount,
|
|
250
|
-
};
|
|
251
|
-
const result = await catchIt(async () => config.handler());
|
|
252
|
-
const endTime = Date.now();
|
|
253
|
-
const duration = endTime - startTime;
|
|
254
|
-
execution.endTime = endTime;
|
|
255
|
-
if (result.isError()) {
|
|
256
|
-
// 执行失败
|
|
257
|
-
const error = new Error(String(result.error));
|
|
258
|
-
execution.success = false;
|
|
259
|
-
execution.error = error;
|
|
260
|
-
task.lastError = error;
|
|
261
|
-
task.retryCount++;
|
|
262
|
-
this.emit('task:error', { taskId: config.id, error, retryCount: task.retryCount });
|
|
263
|
-
// 检查是否需要重试
|
|
264
|
-
if (task.retryCount <= config.maxRetries) {
|
|
265
|
-
this.emit('task:retry', {
|
|
266
|
-
taskId: config.id,
|
|
267
|
-
retryCount: task.retryCount,
|
|
268
|
-
maxRetries: config.maxRetries,
|
|
269
|
-
});
|
|
270
|
-
console.warn(`[Scheduler] Task "${config.id}" failed, will retry (${task.retryCount}/${config.maxRetries})`);
|
|
271
|
-
// 延迟后重试
|
|
272
|
-
await new Promise((resolve) => setTimeout(resolve, config.retryDelay));
|
|
273
|
-
task.running = false;
|
|
274
|
-
return this.executeTask(task);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
// 超过最大重试次数
|
|
278
|
-
this.emit('task:failed', { taskId: config.id, error });
|
|
279
|
-
console.error(`[Scheduler] Task "${config.id}" failed after ${config.maxRetries} retries:`, error.message);
|
|
280
|
-
// 重置重试计数,计划下次执行
|
|
281
|
-
task.retryCount = 0;
|
|
282
|
-
task.nextTick = this.currentTick + Math.ceil(config.interval / Scheduler.TICK_INTERVAL);
|
|
283
|
-
task.running = false;
|
|
284
|
-
task.executions.push(execution);
|
|
285
|
-
return result;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
// 执行成功
|
|
290
|
-
execution.success = true;
|
|
291
|
-
task.retryCount = 0;
|
|
292
|
-
task.lastError = undefined;
|
|
293
|
-
this.emit('task:success', { taskId: config.id, duration });
|
|
294
|
-
// 计划下次执行
|
|
295
|
-
task.nextTick = this.currentTick + Math.ceil(config.interval / Scheduler.TICK_INTERVAL);
|
|
296
|
-
task.running = false;
|
|
297
|
-
task.executions.push(execution);
|
|
298
|
-
// 保留最近 100 条执行记录
|
|
299
|
-
if (task.executions.length > 100) {
|
|
300
|
-
task.executions = task.executions.slice(-100);
|
|
301
|
-
}
|
|
302
|
-
return result;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* 获取任务执行历史
|
|
307
|
-
*/
|
|
308
|
-
getTaskExecutions(taskId, limit = 10) {
|
|
309
|
-
const task = this.tasks.get(taskId);
|
|
310
|
-
if (!task) {
|
|
311
|
-
return [];
|
|
312
|
-
}
|
|
313
|
-
return task.executions.slice(-limit);
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* 获取调度器统计信息
|
|
317
|
-
*/
|
|
318
|
-
getStats() {
|
|
319
|
-
let enabledTasks = 0;
|
|
320
|
-
let runningTasks = 0;
|
|
321
|
-
for (const task of this.tasks.values()) {
|
|
322
|
-
if (task.config.enabled) {
|
|
323
|
-
enabledTasks++;
|
|
324
|
-
}
|
|
325
|
-
if (task.running) {
|
|
326
|
-
runningTasks++;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return {
|
|
330
|
-
running: this.running,
|
|
331
|
-
currentTick: this.currentTick,
|
|
332
|
-
totalTasks: this.tasks.size,
|
|
333
|
-
enabledTasks,
|
|
334
|
-
runningTasks,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* 清理所有任务
|
|
339
|
-
*/
|
|
340
|
-
clear() {
|
|
341
|
-
if (this.running) {
|
|
342
|
-
console.warn('[Scheduler] Cannot clear tasks while scheduler is running');
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
this.tasks.clear();
|
|
346
|
-
console.log('[Scheduler] All tasks cleared');
|
|
347
|
-
}
|
|
348
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.test.d.ts","sourceRoot":"","sources":["../../source/scheduler/scheduler.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { Scheduler } from './scheduler';
|
|
3
|
-
describe('Scheduler', () => {
|
|
4
|
-
let scheduler;
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
scheduler = new Scheduler();
|
|
7
|
-
vi.useFakeTimers();
|
|
8
|
-
});
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
scheduler.stop();
|
|
11
|
-
scheduler.clear();
|
|
12
|
-
vi.restoreAllMocks();
|
|
13
|
-
vi.useRealTimers();
|
|
14
|
-
});
|
|
15
|
-
describe('任务管理', () => {
|
|
16
|
-
it('应该成功添加任务', () => {
|
|
17
|
-
const handler = vi.fn();
|
|
18
|
-
scheduler.addTask({
|
|
19
|
-
id: 'test-task',
|
|
20
|
-
interval: 10,
|
|
21
|
-
handler,
|
|
22
|
-
});
|
|
23
|
-
expect(scheduler.getTaskIds()).toContain('test-task');
|
|
24
|
-
});
|
|
25
|
-
it('添加任务时 interval 必须是 10 的倍数', () => {
|
|
26
|
-
expect(() => {
|
|
27
|
-
scheduler.addTask({
|
|
28
|
-
id: 'invalid-task',
|
|
29
|
-
interval: 15,
|
|
30
|
-
handler: vi.fn(),
|
|
31
|
-
});
|
|
32
|
-
}).toThrow('must be a multiple of 10');
|
|
33
|
-
});
|
|
34
|
-
it('不应该添加重复 id 的任务', () => {
|
|
35
|
-
scheduler.addTask({
|
|
36
|
-
id: 'test-task',
|
|
37
|
-
interval: 10,
|
|
38
|
-
handler: vi.fn(),
|
|
39
|
-
});
|
|
40
|
-
expect(() => {
|
|
41
|
-
scheduler.addTask({
|
|
42
|
-
id: 'test-task',
|
|
43
|
-
interval: 20,
|
|
44
|
-
handler: vi.fn(),
|
|
45
|
-
});
|
|
46
|
-
}).toThrow('already exists');
|
|
47
|
-
});
|
|
48
|
-
it('应该成功移除任务', () => {
|
|
49
|
-
scheduler.addTask({
|
|
50
|
-
id: 'test-task',
|
|
51
|
-
interval: 10,
|
|
52
|
-
handler: vi.fn(),
|
|
53
|
-
});
|
|
54
|
-
const removed = scheduler.removeTask('test-task');
|
|
55
|
-
expect(removed).toBe(true);
|
|
56
|
-
expect(scheduler.getTaskIds()).not.toContain('test-task');
|
|
57
|
-
});
|
|
58
|
-
it('应该能够启用和禁用任务', () => {
|
|
59
|
-
scheduler.addTask({
|
|
60
|
-
id: 'test-task',
|
|
61
|
-
interval: 10,
|
|
62
|
-
handler: vi.fn(),
|
|
63
|
-
});
|
|
64
|
-
expect(scheduler.disableTask('test-task')).toBe(true);
|
|
65
|
-
const state = scheduler.getTaskState('test-task');
|
|
66
|
-
expect(state?.config.enabled).toBe(false);
|
|
67
|
-
expect(scheduler.enableTask('test-task')).toBe(true);
|
|
68
|
-
const state2 = scheduler.getTaskState('test-task');
|
|
69
|
-
expect(state2?.config.enabled).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe('任务调度', () => {
|
|
73
|
-
it('应该按照指定间隔执行任务', async () => {
|
|
74
|
-
const handler = vi.fn();
|
|
75
|
-
scheduler.addTask({
|
|
76
|
-
id: 'test-task',
|
|
77
|
-
interval: 10,
|
|
78
|
-
handler,
|
|
79
|
-
});
|
|
80
|
-
scheduler.start();
|
|
81
|
-
// 第一次 tick (0s)
|
|
82
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
83
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
84
|
-
// 第二次 tick (10s)
|
|
85
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
86
|
-
expect(handler).toHaveBeenCalledTimes(2);
|
|
87
|
-
// 第三次 tick (20s)
|
|
88
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
89
|
-
expect(handler).toHaveBeenCalledTimes(3);
|
|
90
|
-
});
|
|
91
|
-
it('应该支持不同间隔的多个任务', async () => {
|
|
92
|
-
const handler1 = vi.fn();
|
|
93
|
-
const handler2 = vi.fn();
|
|
94
|
-
scheduler.addTask({
|
|
95
|
-
id: 'task-10s',
|
|
96
|
-
interval: 10,
|
|
97
|
-
handler: handler1,
|
|
98
|
-
});
|
|
99
|
-
scheduler.addTask({
|
|
100
|
-
id: 'task-30s',
|
|
101
|
-
interval: 30,
|
|
102
|
-
handler: handler2,
|
|
103
|
-
});
|
|
104
|
-
scheduler.start();
|
|
105
|
-
// 10s
|
|
106
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
107
|
-
expect(handler1).toHaveBeenCalledTimes(1);
|
|
108
|
-
expect(handler2).toHaveBeenCalledTimes(0);
|
|
109
|
-
// 20s
|
|
110
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
111
|
-
expect(handler1).toHaveBeenCalledTimes(2);
|
|
112
|
-
expect(handler2).toHaveBeenCalledTimes(0);
|
|
113
|
-
// 30s
|
|
114
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
115
|
-
expect(handler1).toHaveBeenCalledTimes(3);
|
|
116
|
-
expect(handler2).toHaveBeenCalledTimes(1);
|
|
117
|
-
});
|
|
118
|
-
it('immediate 参数应该使任务立即执行', async () => {
|
|
119
|
-
const handler = vi.fn();
|
|
120
|
-
scheduler.addTask({
|
|
121
|
-
id: 'immediate-task',
|
|
122
|
-
interval: 10,
|
|
123
|
-
handler,
|
|
124
|
-
immediate: true,
|
|
125
|
-
});
|
|
126
|
-
scheduler.start();
|
|
127
|
-
// 立即执行
|
|
128
|
-
await vi.runAllTimersAsync();
|
|
129
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
130
|
-
});
|
|
131
|
-
it('禁用的任务不应该执行', async () => {
|
|
132
|
-
const handler = vi.fn();
|
|
133
|
-
scheduler.addTask({
|
|
134
|
-
id: 'disabled-task',
|
|
135
|
-
interval: 10,
|
|
136
|
-
handler,
|
|
137
|
-
enabled: false,
|
|
138
|
-
});
|
|
139
|
-
scheduler.start();
|
|
140
|
-
await vi.advanceTimersByTimeAsync(20000);
|
|
141
|
-
expect(handler).not.toHaveBeenCalled();
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
describe('重试机制', () => {
|
|
145
|
-
it('失败的任务应该自动重试', async () => {
|
|
146
|
-
let attemptCount = 0;
|
|
147
|
-
const handler = vi.fn(async () => {
|
|
148
|
-
attemptCount++;
|
|
149
|
-
if (attemptCount < 3) {
|
|
150
|
-
throw new Error('Task failed');
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
const errorListener = vi.fn();
|
|
154
|
-
const retryListener = vi.fn();
|
|
155
|
-
const successListener = vi.fn();
|
|
156
|
-
scheduler.on('task:error', errorListener);
|
|
157
|
-
scheduler.on('task:retry', retryListener);
|
|
158
|
-
scheduler.on('task:success', successListener);
|
|
159
|
-
scheduler.addTask({
|
|
160
|
-
id: 'retry-task',
|
|
161
|
-
interval: 10,
|
|
162
|
-
handler,
|
|
163
|
-
maxRetries: 3,
|
|
164
|
-
retryDelay: 100,
|
|
165
|
-
});
|
|
166
|
-
scheduler.start();
|
|
167
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
168
|
-
await vi.advanceTimersByTimeAsync(500); // 等待重试
|
|
169
|
-
expect(handler).toHaveBeenCalledTimes(3);
|
|
170
|
-
expect(errorListener).toHaveBeenCalledTimes(2);
|
|
171
|
-
expect(retryListener).toHaveBeenCalledTimes(2);
|
|
172
|
-
expect(successListener).toHaveBeenCalledTimes(1);
|
|
173
|
-
});
|
|
174
|
-
it('超过最大重试次数后应该触发 task:failed 事件', async () => {
|
|
175
|
-
const handler = vi.fn(async () => {
|
|
176
|
-
throw new Error('Always fails');
|
|
177
|
-
});
|
|
178
|
-
const failedListener = vi.fn();
|
|
179
|
-
scheduler.on('task:failed', failedListener);
|
|
180
|
-
scheduler.addTask({
|
|
181
|
-
id: 'failing-task',
|
|
182
|
-
interval: 10,
|
|
183
|
-
handler,
|
|
184
|
-
maxRetries: 2,
|
|
185
|
-
retryDelay: 100,
|
|
186
|
-
});
|
|
187
|
-
scheduler.start();
|
|
188
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
189
|
-
await vi.advanceTimersByTimeAsync(500); // 等待重试
|
|
190
|
-
expect(handler).toHaveBeenCalledTimes(3); // 1 次初始 + 2 次重试
|
|
191
|
-
expect(failedListener).toHaveBeenCalledTimes(1);
|
|
192
|
-
});
|
|
193
|
-
it('失败后应该在下个周期重新执行', async () => {
|
|
194
|
-
let shouldFail = true;
|
|
195
|
-
const handler = vi.fn(async () => {
|
|
196
|
-
if (shouldFail) {
|
|
197
|
-
throw new Error('First execution fails');
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
scheduler.addTask({
|
|
201
|
-
id: 'recover-task',
|
|
202
|
-
interval: 10,
|
|
203
|
-
handler,
|
|
204
|
-
maxRetries: 0, // 不重试
|
|
205
|
-
});
|
|
206
|
-
scheduler.start();
|
|
207
|
-
// 第一次执行 - 失败
|
|
208
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
209
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
210
|
-
shouldFail = false;
|
|
211
|
-
// 第二次执行 - 成功
|
|
212
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
213
|
-
expect(handler).toHaveBeenCalledTimes(2);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
describe('事件系统', () => {
|
|
217
|
-
it('应该触发 scheduler:start 事件', () => {
|
|
218
|
-
const listener = vi.fn();
|
|
219
|
-
scheduler.on('scheduler:start', listener);
|
|
220
|
-
scheduler.start();
|
|
221
|
-
expect(listener).toHaveBeenCalledTimes(1);
|
|
222
|
-
});
|
|
223
|
-
it('应该触发 scheduler:stop 事件', () => {
|
|
224
|
-
const listener = vi.fn();
|
|
225
|
-
scheduler.on('scheduler:stop', listener);
|
|
226
|
-
scheduler.start();
|
|
227
|
-
scheduler.stop();
|
|
228
|
-
expect(listener).toHaveBeenCalledTimes(1);
|
|
229
|
-
});
|
|
230
|
-
it('应该触发 scheduler:tick 事件', async () => {
|
|
231
|
-
const listener = vi.fn();
|
|
232
|
-
scheduler.on('scheduler:tick', listener);
|
|
233
|
-
scheduler.start();
|
|
234
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
235
|
-
expect(listener).toHaveBeenCalledWith({ tick: 1 });
|
|
236
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
237
|
-
expect(listener).toHaveBeenCalledWith({ tick: 2 });
|
|
238
|
-
});
|
|
239
|
-
it('应该触发 task:start 和 task:success 事件', async () => {
|
|
240
|
-
const startListener = vi.fn();
|
|
241
|
-
const successListener = vi.fn();
|
|
242
|
-
scheduler.on('task:start', startListener);
|
|
243
|
-
scheduler.on('task:success', successListener);
|
|
244
|
-
scheduler.addTask({
|
|
245
|
-
id: 'success-task',
|
|
246
|
-
interval: 10,
|
|
247
|
-
handler: vi.fn(),
|
|
248
|
-
});
|
|
249
|
-
scheduler.start();
|
|
250
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
251
|
-
expect(startListener).toHaveBeenCalledWith({ taskId: 'success-task' });
|
|
252
|
-
expect(successListener).toHaveBeenCalledWith({
|
|
253
|
-
taskId: 'success-task',
|
|
254
|
-
duration: expect.any(Number)
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
describe('手动触发', () => {
|
|
259
|
-
it('应该能够手动触发任务执行', async () => {
|
|
260
|
-
const handler = vi.fn();
|
|
261
|
-
scheduler.addTask({
|
|
262
|
-
id: 'manual-task',
|
|
263
|
-
interval: 60,
|
|
264
|
-
handler,
|
|
265
|
-
});
|
|
266
|
-
const result = await scheduler.triggerTask('manual-task');
|
|
267
|
-
expect(result.isError()).toBe(false);
|
|
268
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
269
|
-
});
|
|
270
|
-
it('手动触发不存在的任务应该返回错误', async () => {
|
|
271
|
-
const result = await scheduler.triggerTask('non-existent');
|
|
272
|
-
expect(result.isError()).toBe(true);
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
describe('统计信息', () => {
|
|
276
|
-
it('应该正确返回统计信息', () => {
|
|
277
|
-
scheduler.addTask({
|
|
278
|
-
id: 'task-1',
|
|
279
|
-
interval: 10,
|
|
280
|
-
handler: vi.fn(),
|
|
281
|
-
});
|
|
282
|
-
scheduler.addTask({
|
|
283
|
-
id: 'task-2',
|
|
284
|
-
interval: 20,
|
|
285
|
-
handler: vi.fn(),
|
|
286
|
-
enabled: false,
|
|
287
|
-
});
|
|
288
|
-
const stats = scheduler.getStats();
|
|
289
|
-
expect(stats.totalTasks).toBe(2);
|
|
290
|
-
expect(stats.enabledTasks).toBe(1);
|
|
291
|
-
expect(stats.running).toBe(false);
|
|
292
|
-
});
|
|
293
|
-
it('应该记录任务执行历史', async () => {
|
|
294
|
-
const handler = vi.fn();
|
|
295
|
-
scheduler.addTask({
|
|
296
|
-
id: 'history-task',
|
|
297
|
-
interval: 10,
|
|
298
|
-
handler,
|
|
299
|
-
});
|
|
300
|
-
scheduler.start();
|
|
301
|
-
await vi.advanceTimersByTimeAsync(30000); // 执行 3 次
|
|
302
|
-
const executions = scheduler.getTaskExecutions('history-task', 10);
|
|
303
|
-
expect(executions.length).toBe(3);
|
|
304
|
-
expect(executions.every((e) => e.success)).toBe(true);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
describe('边界情况', () => {
|
|
308
|
-
it('重复启动应该被忽略', () => {
|
|
309
|
-
scheduler.start();
|
|
310
|
-
scheduler.start();
|
|
311
|
-
expect(scheduler.isRunning()).toBe(true);
|
|
312
|
-
});
|
|
313
|
-
it('停止未启动的调度器应该正常处理', () => {
|
|
314
|
-
expect(() => scheduler.stop()).not.toThrow();
|
|
315
|
-
});
|
|
316
|
-
it('不应该移除正在运行的任务', async () => {
|
|
317
|
-
const handler = vi.fn(async () => {
|
|
318
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
319
|
-
});
|
|
320
|
-
scheduler.addTask({
|
|
321
|
-
id: 'running-task',
|
|
322
|
-
interval: 10,
|
|
323
|
-
handler,
|
|
324
|
-
});
|
|
325
|
-
scheduler.start();
|
|
326
|
-
await vi.advanceTimersByTimeAsync(10000);
|
|
327
|
-
const removed = scheduler.removeTask('running-task');
|
|
328
|
-
expect(removed).toBe(false);
|
|
329
|
-
});
|
|
330
|
-
it('运行中不应该清空任务', () => {
|
|
331
|
-
scheduler.addTask({
|
|
332
|
-
id: 'task-1',
|
|
333
|
-
interval: 10,
|
|
334
|
-
handler: vi.fn(),
|
|
335
|
-
});
|
|
336
|
-
scheduler.start();
|
|
337
|
-
scheduler.clear();
|
|
338
|
-
expect(scheduler.getTaskIds().length).toBe(1);
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
});
|