-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun-batch.ts
More file actions
62 lines (52 loc) · 1.71 KB
/
Copy pathrun-batch.ts
File metadata and controls
62 lines (52 loc) · 1.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import type { BatchItem } from "../types/batch-item.js";
import type { BatchResult, BatchSummary } from "../types/batch-result.js";
import { toErrorRecord } from "./to-error-record.js";
export interface RunBatchOptions {
concurrency: number;
items: AsyncIterable<BatchItem>;
onResult: (result: BatchResult) => void | Promise<void>;
worker: (item: BatchItem) => Promise<unknown>;
}
const noop = (): undefined => undefined;
export async function runBatch({
items,
concurrency,
worker,
onResult,
}: RunBatchOptions): Promise<BatchSummary> {
const summary: BatchSummary = { total: 0, succeeded: 0, failed: 0 };
const iterator = items[Symbol.asyncIterator]();
let pullLock: Promise<unknown> = Promise.resolve();
function nextItem(): Promise<IteratorResult<BatchItem>> {
const pending = pullLock.then(() => iterator.next());
pullLock = pending.then(noop, noop);
return pending;
}
let emitLock: Promise<unknown> = Promise.resolve();
function emit(result: BatchResult): Promise<unknown> {
emitLock = emitLock.then(() => onResult(result));
return emitLock;
}
async function drain(): Promise<void> {
while (true) {
const next = await nextItem();
if (next.done) {
return;
}
const item = next.value;
summary.total++;
try {
const data = await worker(item);
summary.succeeded++;
await emit({ ...item, ok: true, data });
} catch (err) {
summary.failed++;
await emit({ ...item, ok: false, error: toErrorRecord(err) });
}
}
}
const workerCount = Math.max(1, Math.floor(concurrency));
await Promise.all(Array.from({ length: workerCount }, () => drain()));
await emitLock;
return summary;
}