@augment-vir/common
Version:
A collection of augments, helpers types, functions, and classes for any JavaScript environment.
110 lines (109 loc) • 3.65 kB
JavaScript
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;
}
}