haixunMaster/lib/utils/concurrency.ts

47 lines
1.5 KiB
TypeScript

import { computeStaggerMs, humanPause } from "@/lib/threads-browser/human-behavior";
/** 有限平行執行,批次間加隨機間隔以降低被封風險 */
export async function runWithConcurrency<T, R>(
items: T[],
worker: (item: T, index: number) => Promise<R>,
options?: {
concurrency?: number;
staggerMs?: [number, number];
onProgress?: (done: number, total: number) => void;
shouldAbort?: () => boolean | Promise<boolean>;
}
): Promise<R[]> {
const concurrency = Math.max(1, options?.concurrency ?? 1);
const stagger = options?.staggerMs ?? [3500, 9000];
const results: R[] = new Array(items.length);
let nextIndex = 0;
let done = 0;
async function runWorker() {
while (true) {
if (await options?.shouldAbort?.()) {
throw new Error("__JOB_ABORT__");
}
const index = nextIndex++;
if (index >= items.length) return;
if (index > 0) {
await new Promise((resolve) => setTimeout(resolve, computeStaggerMs(stagger)));
if (Math.random() < 0.15 + Math.random() * 0.18) {
await humanPause(Math.random() < 0.55 ? "short" : "medium");
}
}
if (await options?.shouldAbort?.()) {
throw new Error("__JOB_ABORT__");
}
results[index] = await worker(items[index], index);
done++;
options?.onProgress?.(done, items.length);
}
}
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () =>
runWorker()
);
await Promise.all(workers);
return results;
}