element-web/src/utils/Timer.ts
Michael Telatynski a3f5d207de
Switch from defer to Promise.withResolvers (#29078)
* Switch from defer to PromiseWithResolvers

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add modernizr check

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-08 10:03:43 +00:00

118 lines
3.4 KiB
TypeScript

/*
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.
*/
/**
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!: PromiseWithResolvers<void>;
public constructor(private timeout: number) {
this.setNotStarted();
}
private setNotStarted(): void {
this.timerHandle = undefined;
this.startTs = undefined;
this.deferred = Promise.withResolvers();
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;
}
}