UNPKG

@augment-vir/common

Version:

A collection of augments, helpers types, functions, and classes for any JavaScript environment.

110 lines (109 loc) 3.65 kB
import { check } from '@augment-vir/assert'; import { DeferredPromise } from '@augment-vir/core'; import { defineTypedCustomEvent, ListenTarget } from 'typed-event-target'; /** * The event emitted from a {@link PromiseQueue} instance whenever the queue is updated (an item is * resolved or an item is added). * * @category Promise : Util * @category Package : @augment-vir/common * @package [`@augment-vir/common`](https://www.npmjs.com/package/@augment-vir/common) */ export class PromiseQueueUpdateEvent extends defineTypedCustomEvent()('promise-queue-update') { } /** * A queue that manages its items with promises. * * @category Promise * @category Package : @augment-vir/common * @package [`@augment-vir/common`](https://www.npmjs.com/package/@augment-vir/common) */ export class PromiseQueue extends ListenTarget { queue = []; currentlyAwaiting = undefined; queueItemIds = new Set(); /** The current size of the queue. */ get size() { return this.queue.length; } /** * Add an item to the queue. Only await this if you want to wait for the promise to be added * _and_ resolved (or rejected). * * @returns A promise that resolves at the same time as the added item. */ add(item, id) { const newItem = { original: item, id, wrapper: new DeferredPromise(), }; this.queue.push(newItem); if (id != undefined) { this.queueItemIds.add(id); } this.dispatch(new PromiseQueueUpdateEvent({ detail: { queueSize: this.size, addedItem: newItem, }, })); this.triggerNextQueueItem(); return newItem.wrapper.promise; } /** * Checks if the given id is currently in the queue. * * @returns `true` if the item is in the queue and has not been resolved or rejected. `false` if * the item has never been in the queue or has been resolved / rejected from the queue. */ hasItemById(id) { return this.queueItemIds.has(id); } /** Handles a queue item finishing, whether it be a rejection or a resolution. */ handleItemSettle({ rejection, resolution, }) { const item = this.currentlyAwaiting; if (!item) { throw new Error(`Cannot handle queue item settle without a currently awaited queue item.`); } this.queue.splice(0, 1); this.dispatch(new PromiseQueueUpdateEvent({ detail: { queueSize: this.size, finishedItem: item, }, })); if (rejection) { item.wrapper.reject(rejection); } else { item.wrapper.resolve(resolution); } if (item.id != undefined) { this.queueItemIds.delete(item.id); } this.currentlyAwaiting = undefined; this.triggerNextQueueItem(); return item; } /** * Tries to trigger the next queue item, if there is one. * * @returns Whether a new queue item was triggered or not. `true` if it was, otherwise `false`. */ triggerNextQueueItem() { if (this.currentlyAwaiting || !check.isLengthAtLeast(this.queue, 1)) { return false; } const item = this.queue[0]; this.currentlyAwaiting = item; item.original() .then((result) => { this.handleItemSettle({ resolution: result }); }) .catch((error) => { this.handleItemSettle({ rejection: error }); }); return true; } }