@taicode/common-base 3.1.0 → 3.2.0
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/catch/catch.d.ts +1 -0
- package/output/catch/catch.d.ts.map +1 -1
- 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/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
package/output/catch/catch.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catch.d.ts","sourceRoot":"","sources":["../../source/catch/catch.ts"],"names":[],"mappings":"AAMA,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,OAAO,IAAI,CAAC,SAAS,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAExH;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;CACT;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"catch.d.ts","sourceRoot":"","sources":["../../source/catch/catch.ts"],"names":[],"mappings":"AAMA,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,OAAO,IAAI,CAAC,SAAS,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAExH;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;CACT;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;IACR,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG;IAC9E;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,IAAI,YAAY,CAAC,CAAC,CAAC,CAAA;CACpD,CAAA;AA0BD;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AAChG,wBAAsB,OAAO,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
type CleanupFunction = () => unknown | Promise<unknown>;
|
|
1
2
|
export declare class Disposer {
|
|
2
3
|
private cleanupFunctions;
|
|
3
|
-
addDisposer(cleanupFn:
|
|
4
|
-
dispose(): void
|
|
4
|
+
addDisposer(cleanupFn: CleanupFunction): void;
|
|
5
|
+
dispose(): Promise<void>;
|
|
5
6
|
}
|
|
7
|
+
export {};
|
|
6
8
|
//# sourceMappingURL=disposer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disposer.d.ts","sourceRoot":"","sources":["../../source/disposer/disposer.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAQ;IACnB,OAAO,CAAC,gBAAgB,
|
|
1
|
+
{"version":3,"file":"disposer.d.ts","sourceRoot":"","sources":["../../source/disposer/disposer.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAEvD,qBAAa,QAAQ;IACnB,OAAO,CAAC,gBAAgB,CAAwB;IAGhD,WAAW,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI;IAKvC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAY/B"}
|
|
@@ -6,15 +6,16 @@ export class Disposer {
|
|
|
6
6
|
this.cleanupFunctions.push(cleanupFn);
|
|
7
7
|
}
|
|
8
8
|
// 执行所有清理函数
|
|
9
|
-
dispose() {
|
|
10
|
-
|
|
9
|
+
async dispose() {
|
|
10
|
+
const cleanupFunctions = this.cleanupFunctions;
|
|
11
|
+
this.cleanupFunctions = [];
|
|
12
|
+
for (const cleanupFn of cleanupFunctions) {
|
|
11
13
|
try {
|
|
12
|
-
cleanupFn();
|
|
14
|
+
await cleanupFn();
|
|
13
15
|
}
|
|
14
16
|
catch (error) {
|
|
15
17
|
logger.error('Error during cleanup:', error);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
|
-
this.cleanupFunctions = []; // 清空清理函数列表
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { Disposer } from './disposer.js';
|
|
3
|
+
import { logger } from '../logger/index.js';
|
|
3
4
|
describe('Disposer', () => {
|
|
4
5
|
let disposer;
|
|
5
6
|
beforeEach(() => {
|
|
@@ -12,19 +13,19 @@ describe('Disposer', () => {
|
|
|
12
13
|
disposer.addDisposer(cleanupFn);
|
|
13
14
|
}).not.toThrow();
|
|
14
15
|
});
|
|
15
|
-
it('应该能够添加多个清理函数', () => {
|
|
16
|
+
it('应该能够添加多个清理函数', async () => {
|
|
16
17
|
const cleanupFn1 = vi.fn();
|
|
17
18
|
const cleanupFn2 = vi.fn();
|
|
18
19
|
const cleanupFn3 = vi.fn();
|
|
19
20
|
disposer.addDisposer(cleanupFn1);
|
|
20
21
|
disposer.addDisposer(cleanupFn2);
|
|
21
22
|
disposer.addDisposer(cleanupFn3);
|
|
22
|
-
disposer.dispose();
|
|
23
|
+
await disposer.dispose();
|
|
23
24
|
expect(cleanupFn1).toHaveBeenCalledOnce();
|
|
24
25
|
expect(cleanupFn2).toHaveBeenCalledOnce();
|
|
25
26
|
expect(cleanupFn3).toHaveBeenCalledOnce();
|
|
26
27
|
});
|
|
27
|
-
it('应该能够添加返回不同类型的清理函数', () => {
|
|
28
|
+
it('应该能够添加返回不同类型的清理函数', async () => {
|
|
28
29
|
const voidFn = vi.fn(() => { });
|
|
29
30
|
const stringFn = vi.fn(() => 'cleanup result');
|
|
30
31
|
const numberFn = vi.fn(() => 42);
|
|
@@ -33,7 +34,7 @@ describe('Disposer', () => {
|
|
|
33
34
|
disposer.addDisposer(stringFn);
|
|
34
35
|
disposer.addDisposer(numberFn);
|
|
35
36
|
disposer.addDisposer(promiseFn);
|
|
36
|
-
disposer.dispose();
|
|
37
|
+
await disposer.dispose();
|
|
37
38
|
expect(voidFn).toHaveBeenCalled();
|
|
38
39
|
expect(stringFn).toHaveBeenCalled();
|
|
39
40
|
expect(numberFn).toHaveBeenCalled();
|
|
@@ -41,111 +42,124 @@ describe('Disposer', () => {
|
|
|
41
42
|
});
|
|
42
43
|
});
|
|
43
44
|
describe('dispose', () => {
|
|
44
|
-
it('应该执行所有清理函数', () => {
|
|
45
|
+
it('应该执行所有清理函数', async () => {
|
|
45
46
|
const cleanupFn1 = vi.fn();
|
|
46
47
|
const cleanupFn2 = vi.fn();
|
|
47
48
|
const cleanupFn3 = vi.fn();
|
|
48
49
|
disposer.addDisposer(cleanupFn1);
|
|
49
50
|
disposer.addDisposer(cleanupFn2);
|
|
50
51
|
disposer.addDisposer(cleanupFn3);
|
|
51
|
-
disposer.dispose();
|
|
52
|
+
await disposer.dispose();
|
|
52
53
|
expect(cleanupFn1).toHaveBeenCalledOnce();
|
|
53
54
|
expect(cleanupFn2).toHaveBeenCalledOnce();
|
|
54
55
|
expect(cleanupFn3).toHaveBeenCalledOnce();
|
|
55
56
|
});
|
|
56
|
-
it('应该按添加顺序执行清理函数', () => {
|
|
57
|
+
it('应该按添加顺序执行清理函数', async () => {
|
|
57
58
|
const executionOrder = [];
|
|
58
59
|
disposer.addDisposer(() => executionOrder.push(1));
|
|
59
60
|
disposer.addDisposer(() => executionOrder.push(2));
|
|
60
61
|
disposer.addDisposer(() => executionOrder.push(3));
|
|
61
|
-
disposer.dispose();
|
|
62
|
+
await disposer.dispose();
|
|
62
63
|
expect(executionOrder).toEqual([1, 2, 3]);
|
|
63
64
|
});
|
|
64
|
-
it('
|
|
65
|
+
it('应该按添加顺序执行异步清理函数', async () => {
|
|
66
|
+
const executionOrder = [];
|
|
67
|
+
disposer.addDisposer(async () => {
|
|
68
|
+
await Promise.resolve();
|
|
69
|
+
executionOrder.push(1);
|
|
70
|
+
});
|
|
71
|
+
disposer.addDisposer(async () => {
|
|
72
|
+
await Promise.resolve();
|
|
73
|
+
executionOrder.push(2);
|
|
74
|
+
});
|
|
75
|
+
disposer.addDisposer(async () => {
|
|
76
|
+
await Promise.resolve();
|
|
77
|
+
executionOrder.push(3);
|
|
78
|
+
});
|
|
79
|
+
await disposer.dispose();
|
|
80
|
+
expect(executionOrder).toEqual([1, 2, 3]);
|
|
81
|
+
});
|
|
82
|
+
it('应该处理清理函数中的错误', async () => {
|
|
65
83
|
const workingFn1 = vi.fn();
|
|
66
84
|
const errorFn = vi.fn(() => {
|
|
67
85
|
throw new Error('Cleanup error');
|
|
68
86
|
});
|
|
69
87
|
const workingFn2 = vi.fn();
|
|
70
|
-
// 模拟
|
|
71
|
-
const
|
|
88
|
+
// 模拟 logger.error
|
|
89
|
+
const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
|
|
72
90
|
disposer.addDisposer(workingFn1);
|
|
73
91
|
disposer.addDisposer(errorFn);
|
|
74
92
|
disposer.addDisposer(workingFn2);
|
|
75
93
|
// dispose 不应该抛出错误
|
|
76
|
-
expect(()
|
|
77
|
-
disposer.dispose();
|
|
78
|
-
}).not.toThrow();
|
|
94
|
+
await expect(disposer.dispose()).resolves.toBeUndefined();
|
|
79
95
|
// 所有函数都应该被调用
|
|
80
96
|
expect(workingFn1).toHaveBeenCalled();
|
|
81
97
|
expect(errorFn).toHaveBeenCalled();
|
|
82
98
|
expect(workingFn2).toHaveBeenCalled();
|
|
83
99
|
// 错误应该被记录
|
|
84
|
-
expect(
|
|
85
|
-
|
|
100
|
+
expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', expect.any(Error));
|
|
101
|
+
loggerErrorSpy.mockRestore();
|
|
86
102
|
});
|
|
87
|
-
it('应该在执行后清空清理函数列表', () => {
|
|
103
|
+
it('应该在执行后清空清理函数列表', async () => {
|
|
88
104
|
const cleanupFn = vi.fn();
|
|
89
105
|
disposer.addDisposer(cleanupFn);
|
|
90
|
-
disposer.dispose();
|
|
106
|
+
await disposer.dispose();
|
|
91
107
|
expect(cleanupFn).toHaveBeenCalledOnce();
|
|
92
108
|
// 再次调用 dispose 不应该执行任何函数
|
|
93
|
-
disposer.dispose();
|
|
109
|
+
await disposer.dispose();
|
|
94
110
|
expect(cleanupFn).toHaveBeenCalledOnce();
|
|
95
111
|
});
|
|
96
|
-
it('应该能够在清空后重新添加清理函数', () => {
|
|
112
|
+
it('应该能够在清空后重新添加清理函数', async () => {
|
|
97
113
|
const firstCleanup = vi.fn();
|
|
98
114
|
const secondCleanup = vi.fn();
|
|
99
115
|
disposer.addDisposer(firstCleanup);
|
|
100
|
-
disposer.dispose();
|
|
116
|
+
await disposer.dispose();
|
|
101
117
|
disposer.addDisposer(secondCleanup);
|
|
102
|
-
disposer.dispose();
|
|
118
|
+
await disposer.dispose();
|
|
103
119
|
expect(firstCleanup).toHaveBeenCalledOnce();
|
|
104
120
|
expect(secondCleanup).toHaveBeenCalledOnce();
|
|
105
121
|
});
|
|
106
|
-
it('应该在没有清理函数时安全执行', () => {
|
|
107
|
-
expect(()
|
|
108
|
-
disposer.dispose();
|
|
109
|
-
}).not.toThrow();
|
|
122
|
+
it('应该在没有清理函数时安全执行', async () => {
|
|
123
|
+
await expect(disposer.dispose()).resolves.toBeUndefined();
|
|
110
124
|
});
|
|
111
|
-
it('应该能够多次调用 dispose', () => {
|
|
125
|
+
it('应该能够多次调用 dispose', async () => {
|
|
112
126
|
const cleanupFn = vi.fn();
|
|
113
127
|
disposer.addDisposer(cleanupFn);
|
|
114
|
-
disposer.dispose();
|
|
115
|
-
disposer.dispose();
|
|
116
|
-
disposer.dispose();
|
|
128
|
+
await disposer.dispose();
|
|
129
|
+
await disposer.dispose();
|
|
130
|
+
await disposer.dispose();
|
|
117
131
|
expect(cleanupFn).toHaveBeenCalledOnce();
|
|
118
132
|
});
|
|
119
133
|
});
|
|
120
134
|
describe('实际使用场景', () => {
|
|
121
|
-
it('应该能够清理事件监听器', () => {
|
|
135
|
+
it('应该能够清理事件监听器', async () => {
|
|
122
136
|
const removeEventListener = vi.fn();
|
|
123
137
|
const unsubscribe = vi.fn();
|
|
124
138
|
disposer.addDisposer(removeEventListener);
|
|
125
139
|
disposer.addDisposer(unsubscribe);
|
|
126
|
-
disposer.dispose();
|
|
140
|
+
await disposer.dispose();
|
|
127
141
|
expect(removeEventListener).toHaveBeenCalled();
|
|
128
142
|
expect(unsubscribe).toHaveBeenCalled();
|
|
129
143
|
});
|
|
130
|
-
it('应该能够清理定时器', () => {
|
|
144
|
+
it('应该能够清理定时器', async () => {
|
|
131
145
|
const clearTimeout = vi.fn();
|
|
132
146
|
const clearInterval = vi.fn();
|
|
133
147
|
disposer.addDisposer(() => clearTimeout(123));
|
|
134
148
|
disposer.addDisposer(() => clearInterval(456));
|
|
135
|
-
disposer.dispose();
|
|
149
|
+
await disposer.dispose();
|
|
136
150
|
expect(clearTimeout).toHaveBeenCalledWith(123);
|
|
137
151
|
expect(clearInterval).toHaveBeenCalledWith(456);
|
|
138
152
|
});
|
|
139
|
-
it('应该能够清理资源连接', () => {
|
|
153
|
+
it('应该能够清理资源连接', async () => {
|
|
140
154
|
const closeConnection = vi.fn();
|
|
141
155
|
const releaseResource = vi.fn();
|
|
142
156
|
disposer.addDisposer(() => closeConnection());
|
|
143
157
|
disposer.addDisposer(() => releaseResource());
|
|
144
|
-
disposer.dispose();
|
|
158
|
+
await disposer.dispose();
|
|
145
159
|
expect(closeConnection).toHaveBeenCalled();
|
|
146
160
|
expect(releaseResource).toHaveBeenCalled();
|
|
147
161
|
});
|
|
148
|
-
it('应该与 EventEmitter 配合使用', () => {
|
|
162
|
+
it('应该与 EventEmitter 配合使用', async () => {
|
|
149
163
|
// 模拟 EventEmitter 的使用
|
|
150
164
|
const mockEventEmitter = {
|
|
151
165
|
on: vi.fn().mockReturnValue(vi.fn()), // 返回 off 函数
|
|
@@ -156,25 +170,25 @@ describe('Disposer', () => {
|
|
|
156
170
|
disposer.addDisposer(offFunction1);
|
|
157
171
|
disposer.addDisposer(offFunction2);
|
|
158
172
|
disposer.addDisposer(() => mockEventEmitter.cleanup());
|
|
159
|
-
disposer.dispose();
|
|
173
|
+
await disposer.dispose();
|
|
160
174
|
expect(offFunction1).toHaveBeenCalled();
|
|
161
175
|
expect(offFunction2).toHaveBeenCalled();
|
|
162
176
|
expect(mockEventEmitter.cleanup).toHaveBeenCalled();
|
|
163
177
|
});
|
|
164
178
|
});
|
|
165
179
|
describe('错误处理', () => {
|
|
166
|
-
it('应该捕获并记录同步错误', () => {
|
|
167
|
-
const
|
|
180
|
+
it('应该捕获并记录同步错误', async () => {
|
|
181
|
+
const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
|
|
168
182
|
const error = new Error('Sync error');
|
|
169
183
|
disposer.addDisposer(() => {
|
|
170
184
|
throw error;
|
|
171
185
|
});
|
|
172
|
-
disposer.dispose();
|
|
173
|
-
expect(
|
|
174
|
-
|
|
186
|
+
await disposer.dispose();
|
|
187
|
+
expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', error);
|
|
188
|
+
loggerErrorSpy.mockRestore();
|
|
175
189
|
});
|
|
176
|
-
it('应该处理不同类型的错误', () => {
|
|
177
|
-
const
|
|
190
|
+
it('应该处理不同类型的错误', async () => {
|
|
191
|
+
const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
|
|
178
192
|
disposer.addDisposer(() => {
|
|
179
193
|
throw new TypeError('Type error');
|
|
180
194
|
});
|
|
@@ -184,9 +198,19 @@ describe('Disposer', () => {
|
|
|
184
198
|
disposer.addDisposer(() => {
|
|
185
199
|
throw { message: 'Object error' };
|
|
186
200
|
});
|
|
187
|
-
disposer.dispose();
|
|
188
|
-
expect(
|
|
189
|
-
|
|
201
|
+
await disposer.dispose();
|
|
202
|
+
expect(loggerErrorSpy).toHaveBeenCalledTimes(3);
|
|
203
|
+
loggerErrorSpy.mockRestore();
|
|
204
|
+
});
|
|
205
|
+
it('应该捕获并记录异步错误', async () => {
|
|
206
|
+
const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
|
|
207
|
+
const asyncError = new Error('Async error');
|
|
208
|
+
disposer.addDisposer(async () => {
|
|
209
|
+
throw asyncError;
|
|
210
|
+
});
|
|
211
|
+
await disposer.dispose();
|
|
212
|
+
expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', asyncError);
|
|
213
|
+
loggerErrorSpy.mockRestore();
|
|
190
214
|
});
|
|
191
215
|
});
|
|
192
216
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { FlowQueue } from './flow-queue.js';
|
|
3
|
+
import { logger } from '../logger/index.js';
|
|
3
4
|
describe('FlowQueue', () => {
|
|
4
5
|
let flowQueue;
|
|
5
6
|
let mockProcessor;
|
|
@@ -158,8 +159,8 @@ describe('FlowQueue', () => {
|
|
|
158
159
|
throw new Error('Callback error');
|
|
159
160
|
});
|
|
160
161
|
const normalCallback = vi.fn();
|
|
161
|
-
// Mock
|
|
162
|
-
const
|
|
162
|
+
// Mock logger.error 来验证错误被正确记录
|
|
163
|
+
const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
|
|
163
164
|
mockProcessor = vi.fn().mockResolvedValue(expectedOutput);
|
|
164
165
|
flowQueue = new FlowQueue({ retry: { maxRetries: 3 } }, mockProcessor);
|
|
165
166
|
flowQueue.onGenerate(faultyCallback);
|
|
@@ -168,8 +169,8 @@ describe('FlowQueue', () => {
|
|
|
168
169
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
169
170
|
expect(faultyCallback).toHaveBeenCalled();
|
|
170
171
|
expect(normalCallback).toHaveBeenCalledWith(expectedOutput);
|
|
171
|
-
expect(
|
|
172
|
-
|
|
172
|
+
expect(loggerErrorSpy).toHaveBeenCalledWith('Error in onGenerate callback:', expect.any(Error));
|
|
173
|
+
loggerErrorSpy.mockRestore();
|
|
173
174
|
});
|
|
174
175
|
it('失败的处理不应该触发回调', async () => {
|
|
175
176
|
const testInput = { id: 1, data: 'test' };
|
|
@@ -77,9 +77,10 @@ describe('RingCache', () => {
|
|
|
77
77
|
expect(results.map(e => e.data)).toEqual(['apple', 'apricot']);
|
|
78
78
|
});
|
|
79
79
|
it('应该支持时间范围搜索', () => {
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
80
|
+
const entries = cache.getAll();
|
|
81
|
+
const start = new Date(entries[0].timestamp.getTime() - 1);
|
|
82
|
+
const end = new Date(entries[entries.length - 1].timestamp.getTime() + 1);
|
|
83
|
+
const results = cache.getByTimeRange(start, end);
|
|
83
84
|
expect(results.length).toBe(3);
|
|
84
85
|
});
|
|
85
86
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taicode/common-base",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"author": "Alain",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "",
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"test": "vitest run",
|
|
25
|
-
"build": "tsc -p tsconfig.json",
|
|
25
|
+
"build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
|
-
"dev": "tsc -p tsconfig.json --watch"
|
|
27
|
+
"dev": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --watch"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"output"
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"vitest": "^3.2.4",
|
|
34
|
-
"mobx": "^6.
|
|
34
|
+
"mobx": "^6.15.0"
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/scheduler/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './scheduler';
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from '../event-emitter';
|
|
2
|
-
import { type CatchResult } from '../catch';
|
|
3
|
-
/**
|
|
4
|
-
* 定时任务配置
|
|
5
|
-
*/
|
|
6
|
-
export interface ScheduledTaskConfig {
|
|
7
|
-
/**
|
|
8
|
-
* 任务唯一标识
|
|
9
|
-
*/
|
|
10
|
-
id: string;
|
|
11
|
-
/**
|
|
12
|
-
* 任务执行间隔(秒),必须是 10 的倍数
|
|
13
|
-
*/
|
|
14
|
-
interval: number;
|
|
15
|
-
/**
|
|
16
|
-
* 任务处理函数
|
|
17
|
-
*/
|
|
18
|
-
handler: () => Promise<void> | void;
|
|
19
|
-
/**
|
|
20
|
-
* 最大重试次数,默认 3
|
|
21
|
-
*/
|
|
22
|
-
maxRetries?: number;
|
|
23
|
-
/**
|
|
24
|
-
* 重试间隔(毫秒),默认 1000
|
|
25
|
-
*/
|
|
26
|
-
retryDelay?: number;
|
|
27
|
-
/**
|
|
28
|
-
* 是否在启动时立即执行一次,默认 false
|
|
29
|
-
*/
|
|
30
|
-
immediate?: boolean;
|
|
31
|
-
/**
|
|
32
|
-
* 是否启用,默认 true
|
|
33
|
-
*/
|
|
34
|
-
enabled?: boolean;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* 任务执行记录
|
|
38
|
-
*/
|
|
39
|
-
export interface TaskExecution {
|
|
40
|
-
taskId: string;
|
|
41
|
-
startTime: number;
|
|
42
|
-
endTime?: number;
|
|
43
|
-
success: boolean;
|
|
44
|
-
error?: Error;
|
|
45
|
-
retryCount: number;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* 调度器事件
|
|
49
|
-
*/
|
|
50
|
-
export interface SchedulerEvents {
|
|
51
|
-
'task:start': {
|
|
52
|
-
taskId: string;
|
|
53
|
-
};
|
|
54
|
-
'task:success': {
|
|
55
|
-
taskId: string;
|
|
56
|
-
duration: number;
|
|
57
|
-
};
|
|
58
|
-
'task:error': {
|
|
59
|
-
taskId: string;
|
|
60
|
-
error: Error;
|
|
61
|
-
retryCount: number;
|
|
62
|
-
};
|
|
63
|
-
'task:retry': {
|
|
64
|
-
taskId: string;
|
|
65
|
-
retryCount: number;
|
|
66
|
-
maxRetries: number;
|
|
67
|
-
};
|
|
68
|
-
'task:failed': {
|
|
69
|
-
taskId: string;
|
|
70
|
-
error: Error;
|
|
71
|
-
};
|
|
72
|
-
'scheduler:start': void;
|
|
73
|
-
'scheduler:stop': void;
|
|
74
|
-
'scheduler:tick': {
|
|
75
|
-
tick: number;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* 内部任务状态
|
|
80
|
-
*/
|
|
81
|
-
interface TaskState {
|
|
82
|
-
config: Required<ScheduledTaskConfig>;
|
|
83
|
-
nextTick: number;
|
|
84
|
-
running: boolean;
|
|
85
|
-
retryCount: number;
|
|
86
|
-
lastError?: Error;
|
|
87
|
-
executions: TaskExecution[];
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* 定时任务调度器
|
|
91
|
-
*
|
|
92
|
-
* 以 10 秒为基本时间单位,支持多个不同间隔的定时任务
|
|
93
|
-
*
|
|
94
|
-
* @example
|
|
95
|
-
* ```ts
|
|
96
|
-
* const scheduler = new Scheduler()
|
|
97
|
-
*
|
|
98
|
-
* // 添加每 30 秒执行一次的任务
|
|
99
|
-
* scheduler.addTask({
|
|
100
|
-
* id: 'sync-data',
|
|
101
|
-
* interval: 30,
|
|
102
|
-
* handler: async () => {
|
|
103
|
-
* await syncDataToServer()
|
|
104
|
-
* },
|
|
105
|
-
* maxRetries: 3
|
|
106
|
-
* })
|
|
107
|
-
*
|
|
108
|
-
* // 添加每 60 秒执行一次的任务
|
|
109
|
-
* scheduler.addTask({
|
|
110
|
-
* id: 'cleanup',
|
|
111
|
-
* interval: 60,
|
|
112
|
-
* handler: async () => {
|
|
113
|
-
* await cleanupTempFiles()
|
|
114
|
-
* }
|
|
115
|
-
* })
|
|
116
|
-
*
|
|
117
|
-
* // 启动调度器
|
|
118
|
-
* scheduler.start()
|
|
119
|
-
*
|
|
120
|
-
* // 监听事件
|
|
121
|
-
* scheduler.on('task:error', (taskId, error) => {
|
|
122
|
-
* console.error(`Task ${taskId} failed:`, error)
|
|
123
|
-
* })
|
|
124
|
-
*
|
|
125
|
-
* // 停止调度器
|
|
126
|
-
* scheduler.stop()
|
|
127
|
-
* ```
|
|
128
|
-
*/
|
|
129
|
-
export declare class Scheduler extends EventEmitter<SchedulerEvents> {
|
|
130
|
-
/**
|
|
131
|
-
* 基本时间单位(秒)
|
|
132
|
-
*/
|
|
133
|
-
private static readonly TICK_INTERVAL;
|
|
134
|
-
/**
|
|
135
|
-
* 当前 tick 计数
|
|
136
|
-
*/
|
|
137
|
-
private currentTick;
|
|
138
|
-
/**
|
|
139
|
-
* 定时器 ID
|
|
140
|
-
*/
|
|
141
|
-
private timerId;
|
|
142
|
-
/**
|
|
143
|
-
* 是否正在运行
|
|
144
|
-
*/
|
|
145
|
-
private running;
|
|
146
|
-
/**
|
|
147
|
-
* 任务状态映射
|
|
148
|
-
*/
|
|
149
|
-
private tasks;
|
|
150
|
-
/**
|
|
151
|
-
* 添加定时任务
|
|
152
|
-
*/
|
|
153
|
-
addTask(config: ScheduledTaskConfig): void;
|
|
154
|
-
/**
|
|
155
|
-
* 移除定时任务
|
|
156
|
-
*/
|
|
157
|
-
removeTask(taskId: string): boolean;
|
|
158
|
-
/**
|
|
159
|
-
* 启用任务
|
|
160
|
-
*/
|
|
161
|
-
enableTask(taskId: string): boolean;
|
|
162
|
-
/**
|
|
163
|
-
* 禁用任务
|
|
164
|
-
*/
|
|
165
|
-
disableTask(taskId: string): boolean;
|
|
166
|
-
/**
|
|
167
|
-
* 获取任务状态
|
|
168
|
-
*/
|
|
169
|
-
getTaskState(taskId: string): Readonly<TaskState> | undefined;
|
|
170
|
-
/**
|
|
171
|
-
* 获取所有任务 ID
|
|
172
|
-
*/
|
|
173
|
-
getTaskIds(): string[];
|
|
174
|
-
/**
|
|
175
|
-
* 启动调度器
|
|
176
|
-
*/
|
|
177
|
-
start(): void;
|
|
178
|
-
/**
|
|
179
|
-
* 停止调度器
|
|
180
|
-
*/
|
|
181
|
-
stop(): void;
|
|
182
|
-
/**
|
|
183
|
-
* 是否正在运行
|
|
184
|
-
*/
|
|
185
|
-
isRunning(): boolean;
|
|
186
|
-
/**
|
|
187
|
-
* 获取当前 tick
|
|
188
|
-
*/
|
|
189
|
-
getCurrentTick(): number;
|
|
190
|
-
/**
|
|
191
|
-
* 手动触发任务执行(忽略时间间隔)
|
|
192
|
-
*/
|
|
193
|
-
triggerTask(taskId: string): Promise<CatchResult<void, string>>;
|
|
194
|
-
/**
|
|
195
|
-
* 执行 tick
|
|
196
|
-
*/
|
|
197
|
-
private tick;
|
|
198
|
-
/**
|
|
199
|
-
* 执行任务
|
|
200
|
-
*/
|
|
201
|
-
private executeTask;
|
|
202
|
-
/**
|
|
203
|
-
* 获取任务执行历史
|
|
204
|
-
*/
|
|
205
|
-
getTaskExecutions(taskId: string, limit?: number): TaskExecution[];
|
|
206
|
-
/**
|
|
207
|
-
* 获取调度器统计信息
|
|
208
|
-
*/
|
|
209
|
-
getStats(): {
|
|
210
|
-
running: boolean;
|
|
211
|
-
currentTick: number;
|
|
212
|
-
totalTasks: number;
|
|
213
|
-
enabledTasks: number;
|
|
214
|
-
runningTasks: number;
|
|
215
|
-
};
|
|
216
|
-
/**
|
|
217
|
-
* 清理所有任务
|
|
218
|
-
*/
|
|
219
|
-
clear(): void;
|
|
220
|
-
}
|
|
221
|
-
export {};
|
|
222
|
-
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../source/scheduler/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
-
});
|