返回 DeepSeek-Reasonix
subscriptionScope.ts
根目录 / desktop / frontend / src / lib / subscriptionScope.ts
1 type ListenerSlot<Args extends unknown[]> = { listener?: (...args: Args) => void };
2
3 function bindListener<Args extends unknown[]>(slot: ListenerSlot<Args>, lifecycle: { disposed: boolean }) {
4 return (...args: Args) => { if (!lifecycle.disposed) slot.listener?.(...args); };
5 }
6
7 /** A disposed subscription is inert even if its source already queued delivery. */
8 export function createSubscriptionScope(track: (delta: 1 | -1) => void = () => {}) {
9 const cleanups = new Set<() => void>();
10 const lifecycle = { disposed: false };
11 return {
12 listen<Args extends unknown[]>(register: (listener: (...args: Args) => void) => () => void,
13 listener: (...args: Args) => void): void {
14 if (lifecycle.disposed) return;
15 const slot: ListenerSlot<Args> = { listener };
16 let unsubscribe: () => void;
17 try { unsubscribe = register(bindListener(slot, lifecycle)); }
18 catch (error) { slot.listener = undefined; throw error; }
19 track(1);
20 const cleanup = () => {
21 slot.listener = undefined;
22 try { unsubscribe(); } finally { track(-1); }
23 };
24 if (lifecycle.disposed) cleanup();
25 else cleanups.add(cleanup);
26 },
27 dispose(): void {
28 if (lifecycle.disposed) return;
29 lifecycle.disposed = true;
30 const errors: unknown[] = [];
31 for (const cleanup of cleanups) {
32 try { cleanup(); } catch (error) { errors.push(error); }
33 }
34 cleanups.clear();
35 if (errors.length) throw errors[0];
36 },
37 get size() { return cleanups.size; },
38 };
39 }
40
40 lines TYPESCRIPT