-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathTimer.ts
119 lines (105 loc) · 3.41 KB
/
Timer.ts
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
113
114
115
116
117
118
119
/*
Copyright 2024 New Vector Ltd.
Copyright 2018-2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { type IDeferred, defer } from "matrix-js-sdk/src/utils";
/**
A countdown timer, exposing a promise api.
A timer starts in a non-started state,
and needs to be started by calling `start()`` on it first.
Timers can be `abort()`-ed which makes the promise reject prematurely.
Once a timer is finished or aborted, it can't be started again
(because the promise should not be replaced). Instead, create
a new one through `clone()` or `cloneIfRun()`.
*/
export default class Timer {
private timerHandle?: number;
private startTs?: number;
private deferred!: IDeferred<void>;
public constructor(private timeout: number) {
this.setNotStarted();
}
private setNotStarted(): void {
this.timerHandle = undefined;
this.startTs = undefined;
this.deferred = defer();
this.deferred.promise = this.deferred.promise.finally(() => {
this.timerHandle = undefined;
});
}
private onTimeout = (): void => {
const now = Date.now();
const elapsed = now - this.startTs!;
if (elapsed >= this.timeout) {
this.deferred.resolve();
this.setNotStarted();
} else {
const delta = this.timeout - elapsed;
this.timerHandle = window.setTimeout(this.onTimeout, delta);
}
};
public changeTimeout(timeout: number): void {
if (timeout === this.timeout) {
return;
}
const isSmallerTimeout = timeout < this.timeout;
this.timeout = timeout;
if (this.isRunning() && isSmallerTimeout) {
clearTimeout(this.timerHandle);
this.onTimeout();
}
}
/**
* if not started before, starts the timer.
* @returns {Timer} the same timer
*/
public start(): Timer {
if (!this.isRunning()) {
this.startTs = Date.now();
this.timerHandle = window.setTimeout(this.onTimeout, this.timeout);
}
return this;
}
/**
* (re)start the timer. If it's running, reset the timeout. If not, start it.
* @returns {Timer} the same timer
*/
public restart(): Timer {
if (this.isRunning()) {
// don't clearTimeout here as this method
// can be called in fast succession,
// instead just take note and compare
// when the already running timeout expires
this.startTs = Date.now();
return this;
} else {
return this.start();
}
}
/**
* if the timer is running, abort it,
* and reject the promise for this timer.
* @returns {Timer} the same timer
*/
public abort(): Timer {
if (this.isRunning()) {
clearTimeout(this.timerHandle);
this.deferred.reject(new Error("Timer was aborted."));
this.setNotStarted();
}
return this;
}
/**
*promise that will resolve when the timer elapses,
*or is rejected when abort is called
*@return {Promise}
*/
public finished(): Promise<void> {
return this.deferred.promise;
}
public isRunning(): boolean {
return this.timerHandle !== undefined;
}
}