diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-10-30 17:08:11 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-10-31 19:26:14 +0100 |
commit | 80b99f1f2ae86f0ce9652266daa22040b26a5894 (patch) | |
tree | 50cd24b2cfb91606cfc5c6bdd5209fabc93cd5b5 /language-web/src/main/js/utils | |
parent | chore(web): move logger to utils/ (diff) | |
download | refinery-80b99f1f2ae86f0ce9652266daa22040b26a5894.tar.gz refinery-80b99f1f2ae86f0ce9652266daa22040b26a5894.tar.zst refinery-80b99f1f2ae86f0ce9652266daa22040b26a5894.zip |
chore(web): refactor UpdateService
extract utils/ConditionVariable from xtext/UpdateService as a generally
useful utility class for waiting for conditions
Diffstat (limited to 'language-web/src/main/js/utils')
-rw-r--r-- | language-web/src/main/js/utils/ConditionVariable.ts | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/language-web/src/main/js/utils/ConditionVariable.ts b/language-web/src/main/js/utils/ConditionVariable.ts new file mode 100644 index 00000000..0910dfa6 --- /dev/null +++ b/language-web/src/main/js/utils/ConditionVariable.ts | |||
@@ -0,0 +1,64 @@ | |||
1 | import { getLogger } from './logger'; | ||
2 | import { PendingTask } from './PendingTask'; | ||
3 | |||
4 | const log = getLogger('utils.ConditionVariable'); | ||
5 | |||
6 | export type Condition = () => boolean; | ||
7 | |||
8 | export class ConditionVariable { | ||
9 | condition: Condition; | ||
10 | |||
11 | defaultTimeout: number; | ||
12 | |||
13 | 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 | null = null): 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 | } | ||