aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/utils')
-rw-r--r--subprojects/frontend/src/utils/ConditionVariable.ts64
-rw-r--r--subprojects/frontend/src/utils/PendingTask.ts20
2 files changed, 9 insertions, 75 deletions
diff --git a/subprojects/frontend/src/utils/ConditionVariable.ts b/subprojects/frontend/src/utils/ConditionVariable.ts
deleted file mode 100644
index 1d3431f7..00000000
--- a/subprojects/frontend/src/utils/ConditionVariable.ts
+++ /dev/null
@@ -1,64 +0,0 @@
1import PendingTask from './PendingTask';
2import getLogger from './getLogger';
3
4const log = getLogger('utils.ConditionVariable');
5
6export type Condition = () => boolean;
7
8export default class ConditionVariable {
9 private readonly condition: Condition;
10
11 private readonly defaultTimeout: number;
12
13 private listeners: PendingTask<void>[] = [];
14
15 constructor(condition: Condition, defaultTimeout = 0) {
16 this.condition = condition;
17 this.defaultTimeout = defaultTimeout;
18 }
19
20 async waitFor(timeoutMs?: number | undefined): Promise<void> {
21 if (this.condition()) {
22 return;
23 }
24 const timeoutOrDefault = timeoutMs ?? this.defaultTimeout;
25 let nowMs = Date.now();
26 const endMs = nowMs + timeoutOrDefault;
27 while (!this.condition() && nowMs < endMs) {
28 const remainingMs = endMs - nowMs;
29 const promise = new Promise<void>((resolve, reject) => {
30 if (this.condition()) {
31 resolve();
32 return;
33 }
34 const task = new PendingTask(resolve, reject, remainingMs);
35 this.listeners.push(task);
36 });
37 // We must keep waiting until the update has completed,
38 // so the tasks can't be started in parallel.
39 // eslint-disable-next-line no-await-in-loop
40 await promise;
41 nowMs = Date.now();
42 }
43 if (!this.condition()) {
44 log.error('Condition still does not hold after', timeoutOrDefault, 'ms');
45 throw new Error('Failed to wait for condition');
46 }
47 }
48
49 notifyAll(): void {
50 this.clearListenersWith((listener) => listener.resolve());
51 }
52
53 rejectAll(error: unknown): void {
54 this.clearListenersWith((listener) => listener.reject(error));
55 }
56
57 private clearListenersWith(callback: (listener: PendingTask<void>) => void) {
58 // Copy `listeners` so that we don't get into a race condition
59 // if one of the listeners adds another listener.
60 const { listeners } = this;
61 this.listeners = [];
62 listeners.forEach(callback);
63 }
64}
diff --git a/subprojects/frontend/src/utils/PendingTask.ts b/subprojects/frontend/src/utils/PendingTask.ts
index 3976bdf9..205c8452 100644
--- a/subprojects/frontend/src/utils/PendingTask.ts
+++ b/subprojects/frontend/src/utils/PendingTask.ts
@@ -14,21 +14,19 @@ export default class PendingTask<T> {
14 constructor( 14 constructor(
15 resolveCallback: (value: T) => void, 15 resolveCallback: (value: T) => void,
16 rejectCallback: (reason?: unknown) => void, 16 rejectCallback: (reason?: unknown) => void,
17 timeoutMs?: number | undefined, 17 timeoutMs: number | undefined,
18 timeoutCallback?: () => void | undefined, 18 timeoutCallback: () => void | undefined,
19 ) { 19 ) {
20 this.resolveCallback = resolveCallback; 20 this.resolveCallback = resolveCallback;
21 this.rejectCallback = rejectCallback; 21 this.rejectCallback = rejectCallback;
22 if (timeoutMs) { 22 this.timeout = setTimeout(() => {
23 this.timeout = setTimeout(() => { 23 if (!this.resolved) {
24 if (!this.resolved) { 24 this.reject(new Error('Request timed out'));
25 this.reject(new Error('Request timed out')); 25 if (timeoutCallback) {
26 if (timeoutCallback) { 26 timeoutCallback();
27 timeoutCallback();
28 }
29 } 27 }
30 }, timeoutMs); 28 }
31 } 29 }, timeoutMs);
32 } 30 }
33 31
34 resolve(value: T): void { 32 resolve(value: T): void {