chenyc
2025-12-09 545c24c6a711d71b65f3d4e8122fee3837fb1edc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { createBroker } from 'broker-factory';
import { generateUniqueNumber } from 'fast-unique-numbers';
import { TWorkerTimersWorkerDefinition } from 'worker-timers-worker';
import { IWorkerTimersBrokerDefinition } from './interfaces';
import { TWorkerTimersBrokerLoader, TWorkerTimersBrokerWrapper } from './types';
 
/*
 * @todo Explicitly referencing the barrel file seems to be necessary when enabling the
 * isolatedModules compiler option.
 */
export * from './interfaces/index';
export * from './types/index';
 
// Prefilling the Maps with a function indexed by zero is necessary to be compliant with the specification.
const scheduledIntervalsState: Map<number, null | symbol> = new Map([[0, null]]); // tslint:disable-line no-empty
const scheduledTimeoutsState: Map<number, null | symbol> = new Map([[0, null]]); // tslint:disable-line no-empty
 
export const wrap: TWorkerTimersBrokerWrapper = createBroker<IWorkerTimersBrokerDefinition, TWorkerTimersWorkerDefinition>({
    clearInterval: ({ call }) => {
        return (timerId) => {
            if (typeof scheduledIntervalsState.get(timerId) === 'symbol') {
                scheduledIntervalsState.set(timerId, null);
 
                call('clear', { timerId, timerType: 'interval' }).then(() => {
                    scheduledIntervalsState.delete(timerId);
                });
            }
        };
    },
    clearTimeout: ({ call }) => {
        return (timerId) => {
            if (typeof scheduledTimeoutsState.get(timerId) === 'symbol') {
                scheduledTimeoutsState.set(timerId, null);
 
                call('clear', { timerId, timerType: 'timeout' }).then(() => {
                    scheduledTimeoutsState.delete(timerId);
                });
            }
        };
    },
    setInterval: ({ call }) => {
        return (func: Function, delay = 0, ...args: any[]) => {
            const symbol = Symbol();
            const timerId = generateUniqueNumber(scheduledIntervalsState);
 
            scheduledIntervalsState.set(timerId, symbol);
 
            const schedule = () =>
                call('set', {
                    delay,
                    now: performance.timeOrigin + performance.now(),
                    timerId,
                    timerType: 'interval'
                }).then(() => {
                    const state = scheduledIntervalsState.get(timerId);
 
                    if (state === undefined) {
                        throw new Error('The timer is in an undefined state.');
                    }
 
                    if (state === symbol) {
                        func(...args);
 
                        // Doublecheck if the interval should still be rescheduled because it could have been cleared inside of func().
                        if (scheduledIntervalsState.get(timerId) === symbol) {
                            schedule();
                        }
                    }
                });
 
            schedule();
 
            return timerId;
        };
    },
    setTimeout: ({ call }) => {
        return (func: Function, delay = 0, ...args: any[]) => {
            const symbol = Symbol();
            const timerId = generateUniqueNumber(scheduledTimeoutsState);
 
            scheduledTimeoutsState.set(timerId, symbol);
 
            call('set', {
                delay,
                now: performance.timeOrigin + performance.now(),
                timerId,
                timerType: 'timeout'
            }).then(() => {
                const state = scheduledTimeoutsState.get(timerId);
 
                if (state === undefined) {
                    throw new Error('The timer is in an undefined state.');
                }
 
                if (state === symbol) {
                    // A timeout can be savely deleted because it is only called once.
                    scheduledTimeoutsState.delete(timerId);
 
                    func(...args);
                }
            });
 
            return timerId;
        };
    }
});
 
export const load: TWorkerTimersBrokerLoader = (url: string) => {
    const worker = new Worker(url);
 
    return wrap(worker);
};