UNPKG

@nerjs/batchloader

Version:

`BatchLoader` is a tool for batching data requests with support for deduplication, caching, and parallel task management. It is designed to enhance flexibility and performance in scenarios requiring asynchronous data processing. This module was inspired b

143 lines 5.12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UnlimitedTimekeeper = void 0; const errors_1 = require("../utils/errors"); const is_1 = require("../utils/is"); const task_1 = require("./task"); const debug_1 = require("debug"); const debug = (0, debug_1.default)('batchloader:timekeeper'); class UnlimitedTimekeeper { constructor(options, metrics) { this.options = options; this.metrics = metrics; this.currentTask = null; this.runnedTasks = new Map(); this.tidRunner = null; } current() { if (this.currentTask) return this.currentTask.inner; const task = new task_1.Task(this.options.initialDataFactory()); debug(`Create new task. id="${task.id}"`); this.currentTask = task; this.startRunnerTimeout(); this.metrics?.create?.(); return task.inner; } run() { if (!this.currentTask) return; this.clearRunnerTimeout(); this.metrics?.forcedRun?.(); debug(`The current task is started manually. id="${this.currentTask.id}"`); this.runCurrentTask(); } abort(task, reason) { const target = this.findTask(task); if (target) { debug(`Abort task. id="${target.id}"`); const error = (0, is_1.isLoaderError)(reason) ? reason : new errors_1.AbortError('timekeeper', reason); this.metrics?.abort?.(target.inner, error); switch (target.status) { case 'runned': this.rejectRunnedTask(target, error); break; case 'pending': this.rejectPendingTask(target, error); break; } } } async wait(task) { await this.findTask(task)?.defer.promise; } clear() { this.clearRunnerTimeout(); this.runnedTasks.forEach(task => this.abort(task.inner, new errors_1.SilentAbortError('timekeeper'))); } findTask(taskId) { if (typeof taskId === 'object') return this.findTask(taskId.id); return this.findTaskById(taskId); } findTaskById(id) { if (this.currentTask && this.currentTask.id === id) return this.currentTask; return this.runnedTasks.get(id) || null; } startRunnerTimeout() { this.clearRunnerTimeout(); this.tidRunner = setTimeout(() => { debug(`The current task is started by a timer. id=${this.currentTask?.id}`); this.runCurrentTask(); }, this.options.runMs)?.unref(); } clearRunnerTimeout() { if (this.tidRunner) { clearTimeout(this.tidRunner); this.tidRunner = null; } } runCurrentTask() { if (this.currentTask) { this.runTask(this.currentTask); this.currentTask = null; } } async callRunner(task, signal, toThrow) { try { await this.options.runner(task.inner, signal); } catch (error) { debug(`Aborted runner terminated with an error. id="${task.id}"`); if (toThrow) throw error; } } runTask(task) { task.status = 'runned'; task.controller = task.controller || new AbortController(); task.tid = setTimeout(() => this.abort(task.id, new errors_1.TimeoutError(this.options.timeoutMs)), this.options.timeoutMs)?.unref(); this.runnedTasks.set(task.id, task); this.metrics?.runTask?.(this.runnedTasks.size, task.inner); this.callRunner(task, task.controller.signal, true) .then(() => this.resolveTask(task)) .catch(error => this.rejectRunnedTask(task, error)); } resolveTask(task) { if (task.tid) clearTimeout(task.tid); this.runnedTasks.delete(task.id); if (task.status !== 'runned') return; task.status = 'resolved'; debug(`The task was resolved. id="${task.id}"`); task.defer.resolve(task.inner.data); this.metrics?.resolveTask?.(task.inner); } rejectRunnedTask(task, error) { if (task.tid) clearTimeout(task.tid); task.status = 'rejected'; this.runnedTasks.delete(task.id); task.controller?.abort(error); task.defer.reject(error); this.metrics?.rejectTask?.(error, task.inner); debug(`The task was rejected. id="${task.id}"`); } rejectPendingTask(task, error) { this.clearRunnerTimeout(); this.currentTask = null; this.metrics?.rejectTask?.(error, task.inner); this.callAbortedRunner(task, error); debug(`The task was rejected. id="${task.id}"`); } callAbortedRunner(task, error) { task.status = 'rejected'; if (this.options.callRejectedTask) this.callRunner(task, AbortSignal.abort(error), false); task.defer.reject(error); } } exports.UnlimitedTimekeeper = UnlimitedTimekeeper; //# sourceMappingURL=unlimited.timekeeper.js.map