diff --git a/src/features/metadata-editor/MetadataEditorPage.tsx b/src/features/metadata-editor/MetadataEditorPage.tsx index 8115b8d..312940c 100644 --- a/src/features/metadata-editor/MetadataEditorPage.tsx +++ b/src/features/metadata-editor/MetadataEditorPage.tsx @@ -65,6 +65,23 @@ function fieldStyle(): React.CSSProperties { return { width: '100%' }; } +// Cap how many bulk metadata ops run at once — a large selection would otherwise +// fire hundreds of simultaneous requests (each also spins up a worker enqueue). +const BULK_CONCURRENCY = 6; + +async function settleInBatches( + items: T[], + limit: number, + op: (item: T) => Promise, +): Promise[]> { + const results: PromiseSettledResult[] = []; + for (let i = 0; i < items.length; i += limit) { + const batch = items.slice(i, i + limit); + results.push(...(await Promise.allSettled(batch.map(op)))); + } + return results; +} + /** * `/tracks/:trackId/metadata` — A7 metadata editor: manual edits + AcoustID * match picker with a current-vs-proposed diff. `/metadata/batch` operates on a @@ -723,7 +740,7 @@ function BatchEditor() { setBusy(true); setResult(null); const ids = [...selected]; - const outcomes = await Promise.allSettled(ids.map(op)); + const outcomes = await settleInBatches(ids, BULK_CONCURRENCY, op); const ok = outcomes.filter((o) => o.status === 'fulfilled').length; setResult({ ok, fail: ids.length - ok }); setBusy(false);