@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,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
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disposer.d.ts","sourceRoot":"","sources":["../../source/events/disposer.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAQ;IACnB,OAAO,CAAC,gBAAgB,CAAyB;IAGjD,WAAW,CAAC,SAAS,EAAE,MAAM,OAAO,GAAG,IAAI;IAK3C,OAAO,IAAI,IAAI;CAUhB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class Disposer {
|
|
2
|
+
cleanupFunctions = [];
|
|
3
|
+
// 添加清理函数
|
|
4
|
+
addDisposer(cleanupFn) {
|
|
5
|
+
this.cleanupFunctions.push(cleanupFn);
|
|
6
|
+
}
|
|
7
|
+
// 执行所有清理函数
|
|
8
|
+
dispose() {
|
|
9
|
+
for (const cleanupFn of this.cleanupFunctions) {
|
|
10
|
+
try {
|
|
11
|
+
cleanupFn();
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
console.error('Error during cleanup:', error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
this.cleanupFunctions = []; // 清空清理函数列表
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disposer.test.d.ts","sourceRoot":"","sources":["../../source/events/disposer.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { Disposer } from './disposer';
|
|
3
|
+
describe('Disposer', () => {
|
|
4
|
+
let disposer;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
disposer = new Disposer();
|
|
7
|
+
});
|
|
8
|
+
describe('addDisposer', () => {
|
|
9
|
+
it('应该能够添加清理函数', () => {
|
|
10
|
+
const cleanupFn = vi.fn();
|
|
11
|
+
expect(() => {
|
|
12
|
+
disposer.addDisposer(cleanupFn);
|
|
13
|
+
}).not.toThrow();
|
|
14
|
+
});
|
|
15
|
+
it('应该能够添加多个清理函数', () => {
|
|
16
|
+
const cleanupFn1 = vi.fn();
|
|
17
|
+
const cleanupFn2 = vi.fn();
|
|
18
|
+
const cleanupFn3 = vi.fn();
|
|
19
|
+
disposer.addDisposer(cleanupFn1);
|
|
20
|
+
disposer.addDisposer(cleanupFn2);
|
|
21
|
+
disposer.addDisposer(cleanupFn3);
|
|
22
|
+
disposer.dispose();
|
|
23
|
+
expect(cleanupFn1).toHaveBeenCalledOnce();
|
|
24
|
+
expect(cleanupFn2).toHaveBeenCalledOnce();
|
|
25
|
+
expect(cleanupFn3).toHaveBeenCalledOnce();
|
|
26
|
+
});
|
|
27
|
+
it('应该能够添加返回不同类型的清理函数', () => {
|
|
28
|
+
const voidFn = vi.fn(() => { });
|
|
29
|
+
const stringFn = vi.fn(() => 'cleanup result');
|
|
30
|
+
const numberFn = vi.fn(() => 42);
|
|
31
|
+
const promiseFn = vi.fn(() => Promise.resolve('async cleanup'));
|
|
32
|
+
disposer.addDisposer(voidFn);
|
|
33
|
+
disposer.addDisposer(stringFn);
|
|
34
|
+
disposer.addDisposer(numberFn);
|
|
35
|
+
disposer.addDisposer(promiseFn);
|
|
36
|
+
disposer.dispose();
|
|
37
|
+
expect(voidFn).toHaveBeenCalled();
|
|
38
|
+
expect(stringFn).toHaveBeenCalled();
|
|
39
|
+
expect(numberFn).toHaveBeenCalled();
|
|
40
|
+
expect(promiseFn).toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('dispose', () => {
|
|
44
|
+
it('应该执行所有清理函数', () => {
|
|
45
|
+
const cleanupFn1 = vi.fn();
|
|
46
|
+
const cleanupFn2 = vi.fn();
|
|
47
|
+
const cleanupFn3 = vi.fn();
|
|
48
|
+
disposer.addDisposer(cleanupFn1);
|
|
49
|
+
disposer.addDisposer(cleanupFn2);
|
|
50
|
+
disposer.addDisposer(cleanupFn3);
|
|
51
|
+
disposer.dispose();
|
|
52
|
+
expect(cleanupFn1).toHaveBeenCalledOnce();
|
|
53
|
+
expect(cleanupFn2).toHaveBeenCalledOnce();
|
|
54
|
+
expect(cleanupFn3).toHaveBeenCalledOnce();
|
|
55
|
+
});
|
|
56
|
+
it('应该按添加顺序执行清理函数', () => {
|
|
57
|
+
const executionOrder = [];
|
|
58
|
+
disposer.addDisposer(() => executionOrder.push(1));
|
|
59
|
+
disposer.addDisposer(() => executionOrder.push(2));
|
|
60
|
+
disposer.addDisposer(() => executionOrder.push(3));
|
|
61
|
+
disposer.dispose();
|
|
62
|
+
expect(executionOrder).toEqual([1, 2, 3]);
|
|
63
|
+
});
|
|
64
|
+
it('应该处理清理函数中的错误', () => {
|
|
65
|
+
const workingFn1 = vi.fn();
|
|
66
|
+
const errorFn = vi.fn(() => {
|
|
67
|
+
throw new Error('Cleanup error');
|
|
68
|
+
});
|
|
69
|
+
const workingFn2 = vi.fn();
|
|
70
|
+
// 模拟 console.error
|
|
71
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
72
|
+
disposer.addDisposer(workingFn1);
|
|
73
|
+
disposer.addDisposer(errorFn);
|
|
74
|
+
disposer.addDisposer(workingFn2);
|
|
75
|
+
// dispose 不应该抛出错误
|
|
76
|
+
expect(() => {
|
|
77
|
+
disposer.dispose();
|
|
78
|
+
}).not.toThrow();
|
|
79
|
+
// 所有函数都应该被调用
|
|
80
|
+
expect(workingFn1).toHaveBeenCalled();
|
|
81
|
+
expect(errorFn).toHaveBeenCalled();
|
|
82
|
+
expect(workingFn2).toHaveBeenCalled();
|
|
83
|
+
// 错误应该被记录
|
|
84
|
+
expect(consoleSpy).toHaveBeenCalledWith('Error during cleanup:', expect.any(Error));
|
|
85
|
+
consoleSpy.mockRestore();
|
|
86
|
+
});
|
|
87
|
+
it('应该在执行后清空清理函数列表', () => {
|
|
88
|
+
const cleanupFn = vi.fn();
|
|
89
|
+
disposer.addDisposer(cleanupFn);
|
|
90
|
+
disposer.dispose();
|
|
91
|
+
expect(cleanupFn).toHaveBeenCalledOnce();
|
|
92
|
+
// 再次调用 dispose 不应该执行任何函数
|
|
93
|
+
disposer.dispose();
|
|
94
|
+
expect(cleanupFn).toHaveBeenCalledOnce();
|
|
95
|
+
});
|
|
96
|
+
it('应该能够在清空后重新添加清理函数', () => {
|
|
97
|
+
const firstCleanup = vi.fn();
|
|
98
|
+
const secondCleanup = vi.fn();
|
|
99
|
+
disposer.addDisposer(firstCleanup);
|
|
100
|
+
disposer.dispose();
|
|
101
|
+
disposer.addDisposer(secondCleanup);
|
|
102
|
+
disposer.dispose();
|
|
103
|
+
expect(firstCleanup).toHaveBeenCalledOnce();
|
|
104
|
+
expect(secondCleanup).toHaveBeenCalledOnce();
|
|
105
|
+
});
|
|
106
|
+
it('应该在没有清理函数时安全执行', () => {
|
|
107
|
+
expect(() => {
|
|
108
|
+
disposer.dispose();
|
|
109
|
+
}).not.toThrow();
|
|
110
|
+
});
|
|
111
|
+
it('应该能够多次调用 dispose', () => {
|
|
112
|
+
const cleanupFn = vi.fn();
|
|
113
|
+
disposer.addDisposer(cleanupFn);
|
|
114
|
+
disposer.dispose();
|
|
115
|
+
disposer.dispose();
|
|
116
|
+
disposer.dispose();
|
|
117
|
+
expect(cleanupFn).toHaveBeenCalledOnce();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('实际使用场景', () => {
|
|
121
|
+
it('应该能够清理事件监听器', () => {
|
|
122
|
+
const removeEventListener = vi.fn();
|
|
123
|
+
const unsubscribe = vi.fn();
|
|
124
|
+
disposer.addDisposer(removeEventListener);
|
|
125
|
+
disposer.addDisposer(unsubscribe);
|
|
126
|
+
disposer.dispose();
|
|
127
|
+
expect(removeEventListener).toHaveBeenCalled();
|
|
128
|
+
expect(unsubscribe).toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
it('应该能够清理定时器', () => {
|
|
131
|
+
const clearTimeout = vi.fn();
|
|
132
|
+
const clearInterval = vi.fn();
|
|
133
|
+
disposer.addDisposer(() => clearTimeout(123));
|
|
134
|
+
disposer.addDisposer(() => clearInterval(456));
|
|
135
|
+
disposer.dispose();
|
|
136
|
+
expect(clearTimeout).toHaveBeenCalledWith(123);
|
|
137
|
+
expect(clearInterval).toHaveBeenCalledWith(456);
|
|
138
|
+
});
|
|
139
|
+
it('应该能够清理资源连接', () => {
|
|
140
|
+
const closeConnection = vi.fn();
|
|
141
|
+
const releaseResource = vi.fn();
|
|
142
|
+
disposer.addDisposer(() => closeConnection());
|
|
143
|
+
disposer.addDisposer(() => releaseResource());
|
|
144
|
+
disposer.dispose();
|
|
145
|
+
expect(closeConnection).toHaveBeenCalled();
|
|
146
|
+
expect(releaseResource).toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
it('应该与 EventEmitter 配合使用', () => {
|
|
149
|
+
// 模拟 EventEmitter 的使用
|
|
150
|
+
const mockEventEmitter = {
|
|
151
|
+
on: vi.fn().mockReturnValue(vi.fn()), // 返回 off 函数
|
|
152
|
+
cleanup: vi.fn()
|
|
153
|
+
};
|
|
154
|
+
const offFunction1 = mockEventEmitter.on('event1', vi.fn());
|
|
155
|
+
const offFunction2 = mockEventEmitter.on('event2', vi.fn());
|
|
156
|
+
disposer.addDisposer(offFunction1);
|
|
157
|
+
disposer.addDisposer(offFunction2);
|
|
158
|
+
disposer.addDisposer(() => mockEventEmitter.cleanup());
|
|
159
|
+
disposer.dispose();
|
|
160
|
+
expect(offFunction1).toHaveBeenCalled();
|
|
161
|
+
expect(offFunction2).toHaveBeenCalled();
|
|
162
|
+
expect(mockEventEmitter.cleanup).toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe('错误处理', () => {
|
|
166
|
+
it('应该捕获并记录同步错误', () => {
|
|
167
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
168
|
+
const error = new Error('Sync error');
|
|
169
|
+
disposer.addDisposer(() => {
|
|
170
|
+
throw error;
|
|
171
|
+
});
|
|
172
|
+
disposer.dispose();
|
|
173
|
+
expect(consoleSpy).toHaveBeenCalledWith('Error during cleanup:', error);
|
|
174
|
+
consoleSpy.mockRestore();
|
|
175
|
+
});
|
|
176
|
+
it('应该处理不同类型的错误', () => {
|
|
177
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
178
|
+
disposer.addDisposer(() => {
|
|
179
|
+
throw new TypeError('Type error');
|
|
180
|
+
});
|
|
181
|
+
disposer.addDisposer(() => {
|
|
182
|
+
throw 'String error';
|
|
183
|
+
});
|
|
184
|
+
disposer.addDisposer(() => {
|
|
185
|
+
throw { message: 'Object error' };
|
|
186
|
+
});
|
|
187
|
+
disposer.dispose();
|
|
188
|
+
expect(consoleSpy).toHaveBeenCalledTimes(3);
|
|
189
|
+
consoleSpy.mockRestore();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type OffFn = () => void;
|
|
2
|
+
type Listener<T = unknown> = (data: T) => void;
|
|
3
|
+
export declare class EventEmitter<Events extends object = object> {
|
|
4
|
+
private listeners;
|
|
5
|
+
constructor();
|
|
6
|
+
/**
|
|
7
|
+
* 订阅事件
|
|
8
|
+
* @param type 事件类型
|
|
9
|
+
* @param listener 回调函数
|
|
10
|
+
*/
|
|
11
|
+
on<K extends keyof Events>(type: K, listener: Listener<Events[K]>): OffFn;
|
|
12
|
+
/**
|
|
13
|
+
* 取消订阅
|
|
14
|
+
* @param type 事件类型
|
|
15
|
+
* @param listener 要移除的回调函数(可选)
|
|
16
|
+
*/
|
|
17
|
+
off<K extends keyof Events>(type: K, listener?: Listener<Events[K]>): void;
|
|
18
|
+
/**
|
|
19
|
+
* 触发事件
|
|
20
|
+
* @param type 事件类型
|
|
21
|
+
* @param data 要传递的数据
|
|
22
|
+
*/
|
|
23
|
+
emit<K extends keyof Events>(type: K, data: Events[K]): void;
|
|
24
|
+
/**
|
|
25
|
+
* 一次性订阅
|
|
26
|
+
* @param type 事件类型
|
|
27
|
+
* @param listener 回调函数
|
|
28
|
+
*/
|
|
29
|
+
once<K extends keyof Events>(type: K, listener: Listener<Events[K]>): void;
|
|
30
|
+
cleanup(): void;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=event-emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-emitter.d.ts","sourceRoot":"","sources":["../../source/events/event-emitter.ts"],"names":[],"mappings":"AAAA,KAAK,KAAK,GAAG,MAAM,IAAI,CAAA;AACvB,KAAK,QAAQ,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;AAE9C,qBAAa,YAAY,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM;IACtD,OAAO,CAAC,SAAS,CAAsD;;IAUvE;;;;OAIG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK;IASzE;;;;OAIG;IACH,GAAG,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAc1E;;;;OAIG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI;IAQ5D;;;;OAIG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAQ1E,OAAO;CAGR"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export class EventEmitter {
|
|
2
|
+
listeners = {};
|
|
3
|
+
constructor() {
|
|
4
|
+
this.on = this.on.bind(this);
|
|
5
|
+
this.off = this.off.bind(this);
|
|
6
|
+
this.once = this.once.bind(this);
|
|
7
|
+
this.emit = this.emit.bind(this);
|
|
8
|
+
this.cleanup = this.cleanup.bind(this);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 订阅事件
|
|
12
|
+
* @param type 事件类型
|
|
13
|
+
* @param listener 回调函数
|
|
14
|
+
*/
|
|
15
|
+
on(type, listener) {
|
|
16
|
+
if (!this.listeners[type]) {
|
|
17
|
+
this.listeners[type] = [];
|
|
18
|
+
}
|
|
19
|
+
this.listeners[type].push(listener);
|
|
20
|
+
return () => this.off(type, listener);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 取消订阅
|
|
24
|
+
* @param type 事件类型
|
|
25
|
+
* @param listener 要移除的回调函数(可选)
|
|
26
|
+
*/
|
|
27
|
+
off(type, listener) {
|
|
28
|
+
if (!this.listeners[type])
|
|
29
|
+
return;
|
|
30
|
+
if (!listener) {
|
|
31
|
+
// 移除该事件的所有监听器
|
|
32
|
+
delete this.listeners[type];
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// 移除特定监听器
|
|
36
|
+
this.listeners[type] = this.listeners[type].filter((fn) => fn !== listener);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 触发事件
|
|
41
|
+
* @param type 事件类型
|
|
42
|
+
* @param data 要传递的数据
|
|
43
|
+
*/
|
|
44
|
+
emit(type, data) {
|
|
45
|
+
const listeners = this.listeners[type];
|
|
46
|
+
// 复制数组避免回调中取消订阅导致的遍历问题
|
|
47
|
+
if (listeners) {
|
|
48
|
+
listeners.slice().forEach((fn) => fn(data));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 一次性订阅
|
|
53
|
+
* @param type 事件类型
|
|
54
|
+
* @param listener 回调函数
|
|
55
|
+
*/
|
|
56
|
+
once(type, listener) {
|
|
57
|
+
const onceWrapper = (data) => {
|
|
58
|
+
listener(data);
|
|
59
|
+
this.off(type, onceWrapper);
|
|
60
|
+
};
|
|
61
|
+
this.on(type, onceWrapper);
|
|
62
|
+
}
|
|
63
|
+
cleanup() {
|
|
64
|
+
this.listeners = {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-emitter.test.d.ts","sourceRoot":"","sources":["../../source/events/event-emitter.test.ts"],"names":[],"mappings":""}
|