UNPKG

@v4fire/core

Version:
213 lines (173 loc) 4.11 kB
/*! * 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)); } } }