UNPKG

@buttercup/channel-queue

Version:

A queue management library with channels

269 lines (268 loc) 8.09 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Channel = void 0; const eventemitter3_1 = require("eventemitter3"); const layerr_1 = require("layerr"); const Task_1 = require("./Task"); const types_1 = require("./types"); /** * Compare two tasks for sorting * @param {Task} taskA A task * @param {Task} taskB Another task * @returns {Number} The sorting operation regarding the provided tasks * @private * @static * @memberof Channel */ function compareTasks(taskA, taskB) { const { type: typeA, created: createdA } = taskA; const { type: typeB, created: createdB } = taskB; // Sort by priority: if (typeA === types_1.TaskPriority.High && typeB !== types_1.TaskPriority.High) { // A is high priority, and B isn't return -1; } else if (typeB === types_1.TaskPriority.High && typeA !== types_1.TaskPriority.High) { // B is high priority, and A isn't return 1; } else if (typeB === types_1.TaskPriority.Tail && typeA !== types_1.TaskPriority.Tail) { // B is a tail-task, and A isn't return -1; } else if (typeA === types_1.TaskPriority.Tail && typeB !== types_1.TaskPriority.Tail) { // A is a tail-task, and B isn't return 1; } // Sort by created time: if (createdA < createdB) { // A is older return -1; } else if (createdB < createdA) { // B is older return 1; } // Equal priority return 0; } /** * Channel class (queue) * @augments EventEmitter */ class Channel extends eventemitter3_1.EventEmitter { /** * Constructor for a Channel * @param {String} name The name of the channel * @memberof Channel */ constructor(name) { super(); this._taskErrors = []; this._tasksThrow = true; if (typeof name !== "string" || name.length <= 0) { throw new Error("Failed creating Channel: Invalid or empty name"); } this._name = name; this._tasks = []; this._running = false; this._autostart = true; } /** * Whether the execution should start automatically or not * Defaults to true * @type {Boolean} * @memberof Channel */ get autostart() { return this._autostart; } /** * Whether the queue is empty or not * @type {Boolean} * @readonly * @memberof Channel */ get isEmpty() { return !this.isRunning && this.tasks.length === 0; } /** * Whether the queue is currently running or not * @type {Boolean} * @readonly * @memberof Channel */ get isRunning() { return this._running; } /** * The name of the channel * @type {String} * @readonly * @memberof Channel */ get name() { return this._name; } /** * Array of tasks (in queue) * @type {Array.<Task>} * @readonly * @memberof Channel */ get tasks() { return this._tasks; } /** * Whether or not tasks throw errors * @type {Boolean} * @memberof Channel */ get tasksThrow() { return this._tasksThrow; } set autostart(auto) { this._autostart = !!auto; } set isRunning(isRunning) { this._running = isRunning; } set tasksThrow(tasksThrow) { this._tasksThrow = tasksThrow; } /** * Remove all pending tasks from the channel * @param {String=} priorityType Optional priority type to clear * only tasks with a certain priority value * @memberof Channel */ clear(priorityType) { if (!priorityType) { this.tasks.splice(0, Infinity); return; } for (let i = this.tasks.length - 1; i >= 0; i -= 1) { if (this.tasks[i].type === priorityType) { this.tasks.splice(i, 1); } } } /** * Enqueues a function * @param {Function|Promise} item The item to place into the queue * @param {TaskPriority=} type The task priority to use * @param {String=} stack The stack name * @param {Number=} timeout Optional millisecond time-limt * @returns {Promise} A promise that eventually resolves with the result from the * enqueued function or promise * @memberof Channel */ enqueue(item, type = types_1.TaskPriority.Normal, stack, timeout) { if (stack) { const stackItems = this.getStackedItems(stack); if (stackItems.length > 0) { return stackItems[stackItems.length - 1].queuedPromise; } } const task = new Task_1.Task(item, type, stack); if (typeof timeout === "number" && timeout >= 0) { task.timeLimit = timeout; } this.tasks.push(task); this.sort(); if (this.autostart) { this.start(); } return task.queuedPromise; } /** * Get all task items for a stack name * @param {String} stack The stack name * @returns {Array.<Task>} An array of task instances * @memberof Channel */ getStackedItems(stack) { return this.tasks.filter(task => task.stack && task.stack === stack); } /** * Get the next queued Task instance * This modifies the task queue by removing the task * @returns {Task|undefined} A task instance if there are any in queue * @memberof Channel */ retrieveNextItem() { return this.tasks.shift(); } /** * Sort the tasks * @memberof Channel */ sort() { this.tasks.sort(compareTasks); } /** * Start processing the queue * Will automatically return early if queue has already started * @fires Channel#started * @fires Channel#stopped * @returns {Boolean} Returns true if started, false if already started * @memberof Channel */ start() { if (this.isRunning) { return false; } this.emit("started"); this.isRunning = true; setTimeout(() => this._runNextItem(), 0); return true; } /** * Wait for the queue to become empty * @returns {Promise} */ waitForEmpty(opts = {}) { return __awaiter(this, void 0, void 0, function* () { const { throwForFailures = false } = opts; yield new Promise(resolve => { if (this.isEmpty) return resolve(); this.once("stopped", () => { resolve(); }); }); if (throwForFailures && this._taskErrors.length > 0) { throw new layerr_1.Layerr(this._taskErrors[0], "Enqueued task failed"); } }); } _runNextItem() { const item = this.retrieveNextItem(); if (!item) { this.isRunning = false; this.emit("stopped"); } else { item.execute(this._tasksThrow) .then(() => { if (item.error) { this._taskErrors.push(item.error); } }) .then(() => this._runNextItem()) .catch(err => { console.error(err); }); } } } exports.Channel = Channel;