@v4fire/core
Version:
V4Fire core library
213 lines (173 loc) • 4.11 kB
text/typescript
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/
import SimpleQueue from 'core/queue/simple';
import AbstractQueue, { InnerQueue, CreateInnerQueue } from 'core/queue/interface';
export * from 'core/queue/interface';
export interface Task<T = unknown, V = unknown> {
task: T;
promise: Promise<V>;
resolve(res: CanPromise<V>): void;
}
export type Tasks<T = unknown> = InnerQueue<T>;
export type CreateTasks<T extends Tasks<any>> = CreateInnerQueue<T>;
export interface QueueWorker<T = unknown, V = unknown> {
(task: T): CanPromise<V>;
}
export interface WorkerQueueOptions<T extends Tasks<any> = Tasks> {
/**
* A factory to create the internal queue to store elements
*/
tasksFactory?: CreateInnerQueue<T>;
/**
* The maximum number of concurrent workers
*/
concurrency?: number;
/**
* How often to update task statuses (in milliseconds)
*/
refreshInterval?: number;
}
/**
* An abstract class for a worker queue data structure
*
* @typeparam T - the task element
* @typeparam V - the worker value
*/
export default abstract class WorkerQueue<T, V = unknown> extends AbstractQueue<T> {
/**
* Type: a queue of tasks
*/
readonly Tasks!: Tasks;
abstract override readonly head: CanUndef<T>;
/** @inheritDoc */
get length(): number {
return this.tasks.length;
}
/**
* The maximum number of concurrent workers
*/
concurrency: number;
/**
* How often to update task statuses (in milliseconds)
*/
refreshInterval: number;
/**
* Number of active workers
*/
activeWorkers: number = 0;
/**
* The worker constructor
*/
protected worker: QueueWorker<T, V>;
/**
* A queue of tasks
*/
protected tasks: this['Tasks'];
/**
* @param worker
* @param [opts] - additional options
*/
protected constructor(worker: QueueWorker<T, V>, opts: WorkerQueueOptions = {}) {
super();
this.worker = worker;
this.concurrency = opts.concurrency ?? 1;
this.refreshInterval = opts.refreshInterval ?? 0;
if (opts.tasksFactory) {
this.createTasks = opts.tasksFactory;
}
this.tasks = this.createTasks();
}
/** @inheritDoc */
abstract override push(task: T): unknown;
/**
* Returns an asynchronous iterator over the queue elements
*/
[Symbol.asyncIterator](): AsyncIterableIterator<T> {
const
clonedQueue = this.clone();
return {
[Symbol.asyncIterator]() {
return this;
},
next(): Promise<IteratorResult<T>> {
return new Promise((resolve) => {
const
done = clonedQueue.length <= 0,
value = clonedQueue.pop();
if (done || value == null) {
return resolve({done: true, value: undefined});
}
return resolve({done, value});
});
}
};
}
/** @inheritDoc */
pop(): CanUndef<T> {
const {head} = this;
this.tasks.shift();
return head;
}
/** @inheritDoc */
clear(): void {
if (this.length > 0) {
this.tasks = this.createTasks();
this.activeWorkers = 0;
}
}
/**
* Returns a new blank internal queue of tasks
*/
protected createTasks: CreateInnerQueue<this['Tasks']> = () => new SimpleQueue();
/**
* Executes a task chunk from the queue
*/
protected abstract perform(): unknown;
/**
* Executes a task chunk from the queue (deferred version)
*/
protected deferPerform(): Promise<unknown> {
const
i = this.refreshInterval;
return new Promise((resolve) => {
const
cb = () => resolve(this.perform());
if (i > 0) {
setTimeout(cb, i);
} else {
cb();
}
});
}
/**
* Starts an execution of tasks from the queue
*/
protected start(): void {
const n = Math.min(
this.concurrency - this.activeWorkers,
this.length
);
for (let i = 0; i < n; i++) {
this.activeWorkers++;
this.perform();
}
}
/**
* Provides a task result to the specified promise resolve function
*
* @param task
* @param resolve
*/
protected resolveTask(task: T, resolve: Function): void {
try {
resolve(this.worker(task));
} catch (error) {
resolve(Promise.reject(error));
}
}
}