UNPKG

express-long-polling

Version:

Library to implement task dispatching with long-polling approach on express server.

180 lines (179 loc) 5.25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LongPollingTaskQueue = exports.LongPollingTask = void 0; const crypto_1 = require("crypto"); class LongPollingTask { id; input; listeners = []; constructor(id, input) { this.id = id; this.input = input; } toJSON() { return { id: this.id, input: this.input, }; } dispatchResult(output) { for (let listener of this.listeners) { try { listener(output); } catch (error) { console.error(error); } } this.listeners.length = 0; this.addListener = listener => { listener(output); }; } addListener(listener) { this.listeners.push(listener); } removeListener(listener) { let index = this.listeners.indexOf(listener); if (index !== -1) { this.listeners.splice(index, 1); } } } exports.LongPollingTask = LongPollingTask; let defaultPollingInterval = 1000 * 30; /** @description redirect with 307 to let client retry */ function defaultOnTimeout(req) { req.res?.redirect(307, req.url); } class LongPollingTaskQueue { pollingInterval; /** * @description list of pending request from workers */ pendingTaskListeners = []; /** * @description list of pending tasks for workers that are not completed */ pendingTasks = []; /** * @description list of all tasks for client, both completed or not completed tasks. */ allTasks = {}; constructor(options) { this.pollingInterval = options?.pollingInterval || defaultPollingInterval; } /** * @description create task from client */ addTask(options) { let id = typeof options.id === 'string' ? options.id : (options.id || crypto_1.randomUUID)(); let task = new LongPollingTask(id, options.input); this.pendingTasks.push(task); this.allTasks[id] = task; let listener = this.pendingTaskListeners.shift(); if (listener) { listener(task); } return { id }; } getFirstTask() { if (this.pendingTasks.length == 0) return null; return this.pendingTasks[0]; } getRandomTask() { if (this.pendingTasks.length == 0) return null; let index = Math.floor(Math.random() * this.pendingTasks.length); return this.pendingTasks[index]; } popTaskById(id) { let index = this.pendingTasks.findIndex(task => task.id == id); if (index === -1) return null; let task = this.pendingTasks[index]; this.pendingTasks.splice(index, 1); return task; } waitTask(req, onTask, onTimeout = defaultOnTimeout) { this.pendingTaskListeners.push(onTask); let remove = () => { let index = this.pendingTaskListeners.indexOf(onTask); if (index !== -1) { this.pendingTaskListeners.splice(index, 1); } }; let timer = setTimeout(() => { remove(); onTimeout(req); }, this.pollingInterval); req.on('end', () => { remove(); clearTimeout(timer); }); } /** * @description get task from worker */ getOrWaitTask(getTask, req, onTask, onTimeout = defaultOnTimeout) { let task = null; if (getTask == 'first') { task = this.getFirstTask(); } else if (getTask == 'random') { task = this.getRandomTask(); } if (task) { onTask(task); } else { this.waitTask(req, onTask, onTimeout); } } /** * @description dispatch result from worker * @returns true if the task is found and deleted * @returns false if the task is not found (maybe already deleted) */ dispatchResult(id, output) { let task = this.popTaskById(id); if (!task) return false; task.dispatchResult(output); return true; } /** * @description get result from client (dispatched from worker) */ getOrWaitResult(id, req, onOutput, onTimeout = defaultOnTimeout) { let task = this.allTasks[id]; if (!task) { throw new Error('Task not found by id: ' + id); } task.addListener(onOutput); let timer = setTimeout(() => { onTimeout(req); task.removeListener(onOutput); }, this.pollingInterval); req.on('end', () => { clearTimeout(timer); task.removeListener(onOutput); }); } /** * @description delete completed task from client (to release memory) * @returns true if the task is found and deleted * @returns false if the task is not found (maybe already deleted) */ deleteTask(id) { if (id in this.allTasks) { delete this.allTasks[id]; return true; } else { return false; } } } exports.LongPollingTaskQueue = LongPollingTaskQueue;