ported some more small stuff on my way to websock

Changed files
+74 -1
src
+1 -1
src/common/async/aborts.js
··· 8 8 9 9 /** 10 10 * Create an abort signal tied to a timeout. 11 - * Replaces `AbortSignal.timeout`, which doesn't consistently abort with a TimeoutError cross env. 11 + * Replaces `AbortSignal.timeout`, which doesn't consistently abort with a `TimeoutError` cross env. 12 12 * 13 13 * @param {number} ms - timeout in milliseconds 14 14 * @returns {TimeoutSignal} the timeout signal
+28
src/common/async/sleep.js
··· 1 + /** @module common/async */ 2 + 3 + /** 4 + * @param {number} ms the number of ms to sleep 5 + * @param {AbortSignal} [signal] an aptional abort signal, to cancel the sleep 6 + * @returns {Promise<void>} 7 + * a promise that resolves after given amount of time, and is interruptable with an abort signal. 8 + */ 9 + export function sleep(ms, signal) { 10 + signal?.throwIfAborted() 11 + 12 + const { resolve, reject, promise } = Promise.withResolvers() 13 + const timeout = setTimeout(resolve, ms) 14 + 15 + if (signal) { 16 + const abortHandler = () => { 17 + clearTimeout(timeout) 18 + reject(signal.reason) 19 + } 20 + 21 + signal.addEventListener('abort', abortHandler) 22 + promise.finally(() => { 23 + signal.removeEventListener('abort', abortHandler) 24 + }) 25 + } 26 + 27 + return promise 28 + }
+45
src/common/strict-map.js
··· 1 + /** @module common */ 2 + 3 + /** 4 + * A map with methods to ensure key presence and safe update. 5 + * 6 + * @template K, V 7 + * @augments {Map<K, V>} 8 + */ 9 + export class StrictMap extends Map { 10 + 11 + /** 12 + * @param {K} key to lookup in the map, throwing is missing 13 + * @returns {V} the value from the map 14 + * @throws {Error} if the key is not present in the map 15 + */ 16 + require(key) { 17 + if (!this.has(key)) throw Error(`key is required but not in the map`) 18 + 19 + const value = /** @type {V} */ (this.get(key)) 20 + return value 21 + } 22 + 23 + /** 24 + * @param {K} key to lookup in the map 25 + * @param {function(): V} maker a callback which will create the value in the map if not present. 26 + * @returns {V} the value from the map, possibly newly created by {maker} 27 + */ 28 + ensure(key, maker) { 29 + if (!this.has(key)) { 30 + this.set(key, maker()) 31 + } 32 + 33 + return /** @type {V} */ (this.get(key)) 34 + } 35 + 36 + /** 37 + * @param {K} key to update in the map 38 + * @param {function(V=): V} update function which returns the new value for the map 39 + */ 40 + update(key, update) { 41 + const current = this.get(key) 42 + this.set(key, update(current)) 43 + } 44 + 45 + }