Add registry for background promises
This commit is contained in:
		
							
								
								
									
										90
									
								
								src/lib/settle.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/lib/settle.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
/**
 | 
			
		||||
 * A registry for tracking background work and reacting once all work has
 | 
			
		||||
 * settled.
 | 
			
		||||
 */
 | 
			
		||||
export class PromiseRegistry {
 | 
			
		||||
  outstanding: Array<TrackedPromise<unknown>>
 | 
			
		||||
  settleCallbacks: Array<() => void>
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.outstanding = []
 | 
			
		||||
    this.settleCallbacks = []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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.attemptCleanUp.bind(this))
 | 
			
		||||
    )
 | 
			
		||||
    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
 | 
			
		||||
      // TODO: debounce?
 | 
			
		||||
      setTimeout(onSettle, 0)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Singleton regsitry for the whole app.
 | 
			
		||||
 */
 | 
			
		||||
export const AppPromises = new PromiseRegistry()
 | 
			
		||||
		Reference in New Issue
	
	Block a user