UNPKG

taskon

Version:

A simple JavaScript/TypeScript tasks queue that supports dynamic concurrency control

238 lines 9.01 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskQueueBase = void 0; const core_1 = __importDefault(require("./core")); const utils_1 = require("./utils"); const PROMISE_QUEUE_CAPACITY = 1; /** * Base task queue with abstract methods implementation and base public methods. */ class TaskQueueBase extends core_1.default { constructor(_a = {}) { var { memorizeTasks = false, stopOnError = true, defaultIncrementalTaskId = true, taskPrioritizationMode = 'head' } = _a, rest = __rest(_a, ["memorizeTasks", "stopOnError", "defaultIncrementalTaskId", "taskPrioritizationMode"]); super(rest); /** @internal */ this.retrying = false; /** @internal */ this.stopped = false; /** @internal */ this.promiseQueueCapacity = PROMISE_QUEUE_CAPACITY; /** @internal */ this.tasksWaitingQueue = []; /** @internal */ this.prioritizedTasksWaitingQueue = []; /** @internal */ this.failedRetryableTaskQueue = []; /** @internal */ this.taskLookup = {}; this.memorizeTasks = memorizeTasks; this.stopOnError = stopOnError; this.defaultIncrementalTaskId = defaultIncrementalTaskId; this.taskPrioritizationMode = taskPrioritizationMode; } /** @internal */ _getAvailablePromiseQueue() { var _a; // Find any queue whose load is under capacity return ((_a = this.promiseQueues.find((queue) => queue.length < this.promiseQueueCapacity)) !== null && _a !== void 0 ? _a : null); } /** @internal */ _pushTaskToWaitingQueue(task) { if (task.priority === 'normal') { this.tasksWaitingQueue.push(task); this._log({ level: 'info', taskId: task.taskId, }, `Pushed task ${task.taskId} to waiting queue`); } else { this.prioritizedTasksWaitingQueue.push(task); this._log({ level: 'info', taskId: task.taskId, }, `Pushed task ${task.taskId} to prioritized waiting queue`); } } /** @internal */ _getNextTask(previousTaskHasRun) { let queue; // If the queue instance is retrying the failed retryable tasks, then it // should first consider the failed retryable task queue if (this.retrying && this.failedRetryableTaskQueue.length) { queue = this.failedRetryableTaskQueue; } // Otherwise (not retrying or retrying while the failed retryable task // queue is empty), then first consider the prioritized task waiting queue else if (this.prioritizedTasksWaitingQueue.length) { queue = this.prioritizedTasksWaitingQueue; } // Otherwise (neither failed retryable task queue nor prioritized task // waiting queue is available), then consider the normal task waiting queue else { queue = this.tasksWaitingQueue; } // If the queue instance is retrying while there is no failed retryable // task queue, mark the queue as not retrying if (this.retrying && !this.failedRetryableTaskQueue.length) { this.retrying = false; } switch (this.taskPrioritizationMode) { case 'head': { return queue.shift(); } case 'head-with-truncation': { this.failedRetryableTaskQueue = []; this.prioritizedTasksWaitingQueue = []; this.tasksWaitingQueue = []; // If the previous task has run, then clear the corresponding waiting // queue and return null if (previousTaskHasRun) { return null; } // Otherwise, pick the first task from the waiting queue and clear the // corresponding waiting queue else { return queue.shift(); } } case 'tail': { return queue.pop(); } case 'tail-with-truncation': { this.failedRetryableTaskQueue = []; this.prioritizedTasksWaitingQueue = []; this.tasksWaitingQueue = []; // If the previous task has run, then clear the corresponding waiting // queue and return null if (previousTaskHasRun) { return null; } // Otherwise, pick the last task from the waiting queue and clear the // corresponding waiting queue else { return queue.pop(); } } } } /** @internal */ _shouldStop(task) { // If the current task has error and the queue should stop on error, then // stop the queue and push the failed task to the failed retryable task // queue if ((task === null || task === void 0 ? void 0 : task.error) && this.stopOnError) { this.failedRetryableTaskQueue.push(task); this._log({ level: 'info', taskId: task.taskId, }, `Stopped queue due to the error ${task.error} from the task \ ${task.taskId}`); return true; } // If the task queue should stop, then stop the queue if (this.stopped) { this._log({ level: 'info', taskId: task === null || task === void 0 ? void 0 : task.taskId, }, `Stopped queue as it should stop`); return true; } return false; } /** @internal */ _createTask(callback, taskId, onStatusUpdate, priority = 'normal') { const finalTaskId = taskId !== null && taskId !== void 0 ? taskId : (0, utils_1.getTaskId)(this.defaultIncrementalTaskId); const task = { taskId: finalTaskId, callback, createdAt: new Date().getTime(), status: 'idle', onStatusUpdate, priority, }; if (this.memorizeTasks) { this.taskLookup[finalTaskId] = task; } return task; } /** * Start the queue execution. */ start() { this._log({ level: 'info', }, 'Start the queue execution'); this.stopped = false; // Wait for appending tasks to waiting queue Promise.resolve().then(() => { // Enumerate through all promise queues and add tasks to the queue if the // queue is not full this.promiseQueues.forEach((queue) => { if (queue.length < this.promiseQueueCapacity) { const task = this._getNextTask(false); if (task) { this._addTask(task); } } }); }); } /** * Stop the queue execution. * Please note, the current ongoing task will not be stopped immediately. */ stop() { this._log({ level: 'info', }, 'Stop the queue execution'); this.stopped = true; } /** * Retry running the queue with failed tasks. * Please note, this method will be effective only when marking "stopOnError" * as "true" for the queue. */ retry() { this._log({ level: 'info', }, 'Retry the queue execution'); this.retrying = true; this.stopped = false; // Wait for appending tasks to waiting queue Promise.resolve().then(() => { // Enumerate through all promise queues and add tasks to the queue if the // queue is not full this.promiseQueues.forEach((queue) => { if (queue.length < this.promiseQueueCapacity) { const task = this._getNextTask(false); if (task) { this._addTask(task); } } }); }); } /** * Check if the queue is manually stopped. */ isManuallyStopped() { return this.stopped; } } exports.TaskQueueBase = TaskQueueBase; exports.default = TaskQueueBase; //# sourceMappingURL=base.js.map