Skip to content

Commit 416f57e

Browse files
committed
chore: rewrite on state machine, add tests
1 parent 5a5bed0 commit 416f57e

3 files changed

Lines changed: 571 additions & 93 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
type TimerState = "initial" | "idle" | "scheduled" | "pending" | "invoking" | "executing" | "completed";
2+
3+
export class TimerExecutor {
4+
private state: TimerState = "initial";
5+
private intervalHandle: ReturnType<typeof setTimeout> | undefined;
6+
private canExecute: boolean = false;
7+
8+
private delay?: number;
9+
private interval?: number;
10+
private repeat?: boolean;
11+
12+
private callback?: () => void;
13+
14+
setCallback(callback: () => void, newCanExecute: boolean): void {
15+
this.callback = callback;
16+
const prevCanExecute = this.canExecute;
17+
this.canExecute = newCanExecute;
18+
19+
if (this.state === "invoking") {
20+
if (prevCanExecute && !newCanExecute) {
21+
// Action just started executing (canExecute went from true to false)
22+
this.onExecutionStart();
23+
return;
24+
}
25+
}
26+
27+
if (this.state === "executing") {
28+
if (!prevCanExecute && newCanExecute) {
29+
// Action completed successfully (canExecute went from false to true)
30+
this.onExecutionFinish();
31+
return;
32+
}
33+
}
34+
35+
this.tryExecute();
36+
}
37+
38+
setParams(delay: number | undefined, interval: number | undefined, repeat: boolean): void {
39+
this.delay = delay;
40+
this.interval = interval;
41+
this.repeat = repeat;
42+
43+
if (this.state === "initial") {
44+
this.scheduleNext();
45+
}
46+
}
47+
48+
get isReady(): boolean {
49+
return this.delay !== undefined && (!this.repeat || this.interval !== undefined);
50+
}
51+
52+
stop(): void {
53+
clearTimeout(this.intervalHandle);
54+
this.intervalHandle = undefined;
55+
this.delay = undefined;
56+
this.interval = undefined;
57+
this.repeat = false;
58+
this.state = "initial";
59+
}
60+
61+
private scheduleNext(): void {
62+
if (!this.isReady || this.state === "executing" || this.state === "completed") {
63+
return;
64+
}
65+
66+
const isFirstTime = this.state === "initial";
67+
const timeout = isFirstTime ? this.delay : this.interval;
68+
69+
this.state = "scheduled";
70+
this.intervalHandle = setTimeout(() => {
71+
this.onTimerFired();
72+
}, timeout);
73+
}
74+
75+
private onTimerFired(): void {
76+
this.state = "pending";
77+
this.tryExecute();
78+
}
79+
80+
private tryExecute(): void {
81+
if (this.state === "pending" && this.canExecute && this.callback) {
82+
this.state = "invoking";
83+
this.callback();
84+
}
85+
}
86+
87+
private onExecutionStart(): void {
88+
this.state = "executing";
89+
}
90+
91+
private onExecutionFinish(): void {
92+
if (this.repeat) {
93+
this.state = "idle";
94+
this.scheduleNext();
95+
} else {
96+
this.state = "completed";
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)