@paroicms/site-generator-plugin
Version:
ParoiCMS Site Generator Plugin
113 lines (112 loc) • 3.61 kB
JavaScript
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,
};
}