UNPKG

@paroicms/site-generator-plugin

Version:

ParoiCMS Site Generator Plugin

113 lines (112 loc) 3.61 kB
import { messageOf } from "@paroi/data-formatters-lib"; export function createTaskCollector(ctx) { const tasks = []; return { tasks, add(task) { tasks.push(task); }, runAll(options) { return runTasks(ctx, tasks, options); }, }; } export function runTasks(ctx, tasks, options) { const { logger } = ctx; const { maxParallel, rateLimitPerSecond } = options; let resolve; let reject; const promise = new Promise((resolveCb, rejectCb) => { resolve = resolveCb; reject = rejectCb; }); let timeoutId; let runningCount = 0; let index = 0; let doneCount = 0; const errorMessages = []; // Track task start timestamps for rate limiting const startTimestamps = []; // Flag to indicate whether the task runner has been stopped let mustStop = false; // Resolve function for stop promise let stopResolve; const stopPromise = new Promise((resolveStop) => { stopResolve = resolveStop; }); function runNext() { // Don't start new tasks if stopped if (mustStop) { if (runningCount === 0) { // All running tasks have completed, we can resolve the stop promise if (index < tasks.length) { reject(new Error("Stopped before all tasks completed")); } else { resolve({ doneCount, errorMessages }); } stopResolve(); } return; } if (index >= tasks.length) { if (runningCount === 0) { resolve({ doneCount, errorMessages }); } return; } if (runningCount >= maxParallel) return; // Check rate limit if specified if (rateLimitPerSecond !== undefined && rateLimitPerSecond > 0) { const now = Date.now(); // Remove timestamps older than 1 second const oneSecondAgo = now - 1000; while (startTimestamps.length > 0 && startTimestamps[0] <= oneSecondAgo) { startTimestamps.shift(); } // Check if we've hit the rate limit if (startTimestamps.length >= rateLimitPerSecond) { // We've reached our rate limit, schedule retry after delay const oldestTimestamp = startTimestamps[0]; const delayMs = Math.max(10, oldestTimestamp + 1000 - now); clearTimeout(timeoutId); timeoutId = setTimeout(runNext, delayMs); return; } // Record this task start time startTimestamps.push(now); } const task = tasks[index]; ++index; ++runningCount; task().then(() => { ++doneCount; --runningCount; runNext(); }, (err) => { logger.error(err); errorMessages.push(messageOf(err)); --runningCount; runNext(); }); } for (let i = 0; i < maxParallel; ++i) { runNext(); } function stop() { // Mark as stopped to prevent new tasks from starting mustStop = true; clearTimeout(timeoutId); // If no tasks are running, resolve immediately if (runningCount === 0) { stopResolve(); } // Otherwise, stopResolve will be called when runningCount reaches 0 return stopPromise; } return { promise, stop, }; }