| ... | ... | @@ -61,6 +61,7 @@ export function RefCountedExpirable<T>( |
| 61 | 61 | }; |
| 62 | 62 | } |
| 63 | 63 | |
| 64 | /** evaluates `fn` one time, caching the result forever. */ |
| 64 | 65 | export function once<T>(fn: () => Promise<T>): () => Promise<T> { |
| 65 | 66 | let result: T | Promise<T> | null = null; |
| 66 | 67 | return async () => { |
| ... | ... | @@ -75,6 +76,7 @@ export function once<T>(fn: () => Promise<T>): () => Promise<T> { |
| 75 | 76 | }; |
| 76 | 77 | } |
| 77 | 78 | |
| 79 | /** a variable that can be watched for changes */ |
| 78 | 80 | export class Watch<T> { |
| 79 | 81 | #value: T; |
| 80 | 82 | #observers = new Set<WatchCallback<T>>(); |
| ... | ... | @@ -116,7 +118,7 @@ export class Watch<T> { |
| 116 | 118 | } |
| 117 | 119 | } |
| 118 | 120 | } |
| 119 | | type WatchCallback<T> = (next: T, prev: T) => void; |
| 121 | export type WatchCallback<T> = (next: T, prev: T) => void; |
| 120 | 122 | |
| 121 | 123 | /** |
| 122 | 124 | * when two requests with the same args come in at the same time, the response |
| ... | ... | @@ -124,10 +126,16 @@ type WatchCallback<T> = (next: T, prev: T) => void; |
| 124 | 126 | */ |
| 125 | 127 | export class DedupeConcurrent<Args extends unknown[], Ret> { |
| 126 | 128 | pending: Map<unknown, Promise<Ret>> = new Map(); |
| 129 | fn: (...args: Args) => Promise<Ret>; |
| 130 | keyFn: (args: Args) => unknown = JSON.stringify; |
| 131 | |
| 127 | 132 | constructor( |
| 128 | | public fn: (...args: Args) => Promise<Ret>, |
| 129 | | public keyFn: (args: Args) => unknown = JSON.stringify, |
| 130 | | ) {} |
| 133 | fn: (...args: Args) => Promise<Ret>, |
| 134 | keyFn: (args: Args) => unknown = JSON.stringify, |
| 135 | ) { |
| 136 | this.fn = fn; |
| 137 | this.keyFn = keyFn; |
| 138 | } |
| 131 | 139 | |
| 132 | 140 | isPending(...args: Args): boolean { |
| 133 | 141 | return this.pending.has(this.keyFn(args)); |
| ... | ... | @@ -146,14 +154,21 @@ export class DedupeConcurrent<Args extends unknown[], Ret> { |
| 146 | 154 | } |
| 147 | 155 | |
| 148 | 156 | /** |
| 149 | | * when two requests with the same args come, the result of the first is memoized. |
| 157 | * when two requests with the same args come, the result |
| 158 | * of the first is memoized. |
| 150 | 159 | */ |
| 151 | 160 | export class OnceMap<Args extends unknown[], Ret> { |
| 152 | 161 | cache: Map<unknown, Promise<Ret>> = new Map(); |
| 162 | fn: (...args: Args) => Promise<Ret>; |
| 163 | keyFn: (args: Args) => unknown = JSON.stringify; |
| 164 | |
| 153 | 165 | constructor( |
| 154 | | public fn: (...args: Args) => Promise<Ret>, |
| 155 | | public keyFn: (args: Args) => unknown = JSON.stringify, |
| 156 | | ) {} |
| 166 | fn: (...args: Args) => Promise<Ret>, |
| 167 | keyFn: (args: Args) => unknown = JSON.stringify, |
| 168 | ) { |
| 169 | this.fn = fn; |
| 170 | this.keyFn = keyFn; |
| 171 | } |
| 157 | 172 | |
| 158 | 173 | has(...args: Args): boolean { |
| 159 | 174 | return this.cache.has(this.keyFn(args)); |
| ... | ... | @@ -204,4 +219,32 @@ export class PromiseAggregator { |
| 204 | 219 | } |
| 205 | 220 | } |
| 206 | 221 | |
| 222 | /** a promise that can be cancelled. */ |
| 223 | export type Cancelable<T> = Promise<T> & Disposable & { cancel: VoidFunction }; |
| 224 | |
| 225 | /** make an existing promise cancelable. */ |
| 226 | export function makeCancelable<T>( |
| 227 | promise: Promise<T>, |
| 228 | cancel: ts.VoidFunction, |
| 229 | ): Cancelable<T> { |
| 230 | const p = promise as Cancelable<T>; |
| 231 | p[Symbol.dispose] = cancel; |
| 232 | p.cancel = cancel; |
| 233 | return p; |
| 234 | } |
| 235 | |
| 236 | /** wait `ms` milliseconds, then resolve. can be cancelled. */ |
| 237 | export function delay(ms: number): Cancelable<void> { |
| 238 | let t: ts.Timer | null = null; |
| 239 | return makeCancelable( |
| 240 | new Promise((resolve) => { |
| 241 | t = globalThis.setTimeout(() => (t = null, resolve()), ms); |
| 242 | }), |
| 243 | () => { |
| 244 | if (t != null) clearTimeout(t); |
| 245 | }, |
| 246 | ); |
| 247 | } |
| 248 | |
| 207 | 249 | import { ASSERT } from "./assert.ts"; |
| 250 | import * as ts from "./ts.ts"; |