Poller.ts (3672B)
1 /** 2 * @license 3 * Copyright 2022 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import {assert} from '../util/assert.js'; 8 import {Deferred} from '../util/Deferred.js'; 9 10 /** 11 * @internal 12 */ 13 export interface Poller<T> { 14 start(): Promise<void>; 15 stop(): Promise<void>; 16 result(): Promise<T>; 17 } 18 19 /** 20 * @internal 21 */ 22 export class MutationPoller<T> implements Poller<T> { 23 #fn: () => Promise<T>; 24 25 #root: Node; 26 27 #observer?: MutationObserver; 28 #deferred?: Deferred<T>; 29 constructor(fn: () => Promise<T>, root: Node) { 30 this.#fn = fn; 31 this.#root = root; 32 } 33 34 async start(): Promise<void> { 35 const deferred = (this.#deferred = Deferred.create<T>()); 36 const result = await this.#fn(); 37 if (result) { 38 deferred.resolve(result); 39 return; 40 } 41 42 this.#observer = new MutationObserver(async () => { 43 const result = await this.#fn(); 44 if (!result) { 45 return; 46 } 47 deferred.resolve(result); 48 await this.stop(); 49 }); 50 this.#observer.observe(this.#root, { 51 childList: true, 52 subtree: true, 53 attributes: true, 54 }); 55 } 56 57 async stop(): Promise<void> { 58 assert(this.#deferred, 'Polling never started.'); 59 if (!this.#deferred.finished()) { 60 this.#deferred.reject(new Error('Polling stopped')); 61 } 62 if (this.#observer) { 63 this.#observer.disconnect(); 64 this.#observer = undefined; 65 } 66 } 67 68 result(): Promise<T> { 69 assert(this.#deferred, 'Polling never started.'); 70 return this.#deferred.valueOrThrow(); 71 } 72 } 73 74 /** 75 * @internal 76 */ 77 export class RAFPoller<T> implements Poller<T> { 78 #fn: () => Promise<T>; 79 #deferred?: Deferred<T>; 80 constructor(fn: () => Promise<T>) { 81 this.#fn = fn; 82 } 83 84 async start(): Promise<void> { 85 const deferred = (this.#deferred = Deferred.create<T>()); 86 const result = await this.#fn(); 87 if (result) { 88 deferred.resolve(result); 89 return; 90 } 91 92 const poll = async () => { 93 if (deferred.finished()) { 94 return; 95 } 96 const result = await this.#fn(); 97 if (!result) { 98 window.requestAnimationFrame(poll); 99 return; 100 } 101 deferred.resolve(result); 102 await this.stop(); 103 }; 104 window.requestAnimationFrame(poll); 105 } 106 107 async stop(): Promise<void> { 108 assert(this.#deferred, 'Polling never started.'); 109 if (!this.#deferred.finished()) { 110 this.#deferred.reject(new Error('Polling stopped')); 111 } 112 } 113 114 result(): Promise<T> { 115 assert(this.#deferred, 'Polling never started.'); 116 return this.#deferred.valueOrThrow(); 117 } 118 } 119 120 /** 121 * @internal 122 */ 123 124 export class IntervalPoller<T> implements Poller<T> { 125 #fn: () => Promise<T>; 126 #ms: number; 127 128 #interval?: NodeJS.Timeout; 129 #deferred?: Deferred<T>; 130 constructor(fn: () => Promise<T>, ms: number) { 131 this.#fn = fn; 132 this.#ms = ms; 133 } 134 135 async start(): Promise<void> { 136 const deferred = (this.#deferred = Deferred.create<T>()); 137 const result = await this.#fn(); 138 if (result) { 139 deferred.resolve(result); 140 return; 141 } 142 143 this.#interval = setInterval(async () => { 144 const result = await this.#fn(); 145 if (!result) { 146 return; 147 } 148 deferred.resolve(result); 149 await this.stop(); 150 }, this.#ms); 151 } 152 153 async stop(): Promise<void> { 154 assert(this.#deferred, 'Polling never started.'); 155 if (!this.#deferred.finished()) { 156 this.#deferred.reject(new Error('Polling stopped')); 157 } 158 if (this.#interval) { 159 clearInterval(this.#interval); 160 this.#interval = undefined; 161 } 162 } 163 164 result(): Promise<T> { 165 assert(this.#deferred, 'Polling never started.'); 166 return this.#deferred.valueOrThrow(); 167 } 168 }