Compare commits
3 Commits
kcl-76
...
jtran/prom
Author | SHA1 | Date | |
---|---|---|---|
5ab814d153 | |||
fe83cd94ca | |||
5178a72c52 |
97
src/lib/settle.ts
Normal file
97
src/lib/settle.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { debounce } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry for tracking background work and reacting once all work has
|
||||||
|
* settled.
|
||||||
|
*/
|
||||||
|
export class PromiseRegistry {
|
||||||
|
outstanding: Array<TrackedPromise<unknown>>
|
||||||
|
settleCallbacks: Array<() => void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This reduces overhead when there are many promises all settling around the
|
||||||
|
* same time by trading some latency for when the overall settling is
|
||||||
|
* detected.
|
||||||
|
*/
|
||||||
|
private debouncedCleanUp: () => void
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.outstanding = []
|
||||||
|
this.settleCallbacks = []
|
||||||
|
this.debouncedCleanUp = debounce(this.attemptCleanUp.bind(this), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
track<T>(promise: Promise<T>, onSettle?: () => void) {
|
||||||
|
// Since built-in Promises don't have a way to synchronously check if
|
||||||
|
// they're settled, it cannot start out settled.
|
||||||
|
this.outstanding.push(new TrackedPromise(promise, this.debouncedCleanUp))
|
||||||
|
if (onSettle) {
|
||||||
|
this.settleCallbacks.push(onSettle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise that resolves when all promises have settled.
|
||||||
|
*/
|
||||||
|
waitForSettle(): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.addSettleCallback(resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a callback to be called when all promises have settled.
|
||||||
|
*
|
||||||
|
* @see waitForSettle for a Promise-based interface.
|
||||||
|
*/
|
||||||
|
addSettleCallback(onSettle: () => void) {
|
||||||
|
if (this.isSettled()) {
|
||||||
|
// Already settled, so schedule the callback.
|
||||||
|
setTimeout(onSettle, 0)
|
||||||
|
} else {
|
||||||
|
this.settleCallbacks.push(onSettle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSettled(): boolean {
|
||||||
|
return this.outstanding.every((p) => p.settled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private attemptCleanUp() {
|
||||||
|
if (this.outstanding.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Garbage collect.
|
||||||
|
const unsettled = this.outstanding.filter((p) => !p.settled)
|
||||||
|
this.outstanding = unsettled
|
||||||
|
// Transition to settled. We could move this into TrackedPromise.
|
||||||
|
// It's a trade-off between reducing latency and reducing overhead.
|
||||||
|
if (unsettled.length === 0) {
|
||||||
|
for (const cb of this.settleCallbacks) {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
this.settleCallbacks = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native Promises don't have a way to synchronously detect if they're settled.
|
||||||
|
*/
|
||||||
|
class TrackedPromise<T> {
|
||||||
|
settled: boolean
|
||||||
|
inner: Promise<T>
|
||||||
|
|
||||||
|
constructor(promise: Promise<T>, onSettle: () => void) {
|
||||||
|
this.settled = false
|
||||||
|
this.inner = promise.finally(() => {
|
||||||
|
this.settled = true
|
||||||
|
onSettle()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton regsitry for the whole app.
|
||||||
|
*/
|
||||||
|
export const AppPromises = new PromiseRegistry()
|
@ -88,6 +88,29 @@ export function normaliseAngle(angle: number): number {
|
|||||||
return result > 180 ? result - 360 : result
|
return result > 180 ? result - 360 : result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function that will delay the execution of the given function each
|
||||||
|
* time it's called until the wait time has passed.
|
||||||
|
*/
|
||||||
|
export function debounce<
|
||||||
|
F extends (...args: any[]) => void,
|
||||||
|
P extends Parameters<F>
|
||||||
|
>(func: F, wait: number): (...args: P) => void {
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
function debounced(...args: P) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null
|
||||||
|
func(args)
|
||||||
|
}, wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
return debounced
|
||||||
|
}
|
||||||
|
|
||||||
export function throttle<T>(
|
export function throttle<T>(
|
||||||
func: (args: T) => any,
|
func: (args: T) => any,
|
||||||
wait: number
|
wait: number
|
||||||
|
Reference in New Issue
Block a user