Process large collections in parallel batches with failure isolation between groups.
Use batching when you need to process a large list of items in parallel while controlling concurrency. Items are split into fixed-size batches, each batch runs concurrently, and failures in one batch don't affect others.
- Bulk data imports (contacts, orders, products from a CSV)
- Processing hundreds or thousands of items against external APIs
- Calling rate-limited APIs where you need to control concurrency
- Any fan-out where you want failure isolation between groups
- Records are split into fixed-size batches.
- Each batch runs in parallel via
Promise.allSettled— failures in one record don't affect others. - A
sleep()between batches paces requests to avoid overloading downstream services. - After all batches, a summary is returned with succeeded/failed counts.
The workflow splits records into chunks, processes each chunk concurrently, tracks results per batch, and returns a final tally.
import { sleep } from "workflow";
type Record = { name: string; email: string; role: string };
declare function processRecord(record: Record): Promise<string>; // @setup
export async function batchImport(records: Record[], batchSize: number) {
"use workflow";
let totalSucceeded = 0;
let totalFailed = 0;
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
// Run batch in parallel — failures are isolated per record
const outcomes = await Promise.allSettled(
batch.map((record) => processRecord(record))
);
for (let j = 0; j < outcomes.length; j++) {
if (outcomes[j].status === "fulfilled") {
totalSucceeded++;
} else {
totalFailed++;
}
}
// Pace between batches to avoid overloading downstream
if (i + batchSize < records.length) {
await sleep("1s");
}
}
return { total: records.length, succeeded: totalSucceeded, failed: totalFailed };
}Step function
Each record is processed in its own step with full Node.js access and automatic retries.
type Record = { name: string; email: string; role: string };
async function processRecord(record: Record): Promise<string> {
"use step";
const res = await fetch(`https://api.example.com/contacts`, {
method: "POST",
body: JSON.stringify(record),
});
if (!res.ok) throw new Error(`Failed to import ${record.email}`);
const { id } = await res.json();
return id;
}- Replace the
Recordtype with your actual data shape (orders, images, products, etc.). - Replace
processRecord()with your real import logic — DB upserts, API calls, file processing. - Tune
batchSizeand thesleep()duration to match your downstream rate limits. - Add or remove tracking as needed — the pattern works with any item type.
- Use
Promise.allSettledoverPromise.allwhen you want to continue even if some items fail.Promise.allrejects on the first failure;allSettledwaits for everything and tells you what failed. - Tune batch size to your downstream API limits. If the API allows 10 concurrent requests, use
batchSize: 10. - Add pacing with
sleep()between batches to respect rate limits. The sleep is durable — it survives cold starts. - Each
processRecordcall is an independent step. If one fails, it retries up to 3 times without affecting other items in the batch.
"use workflow"-- marks the orchestrator function"use step"-- marks functions that run with full Node.js accesssleep()-- pacing delay between batchesPromise.allSettled()-- runs items in parallel, isolating failures












