Skip to content
Prose v0.3.2

Timeouts & Cancellation

Prose supports three layers of timeout and cancellation, all backed by AbortSignal.

Set a maximum duration for the entire flow.

await flow.execute(input, deps, {
timeout: 30_000, // abort if the flow exceeds 30 seconds
});

If the timeout is reached, a TimeoutError is thrown with the flow name and timeout value.

Set a default maximum duration for each step.

await flow.execute(input, deps, {
stepTimeout: 5_000, // abort any step that exceeds 5 seconds
});

You can also override the step timeout for a specific step via .withRetry():

flow
.step('slowOperation', async (ctx) => { /* ... */ })
.withRetry({
maxAttempts: 1,
delayMs: 0,
stepTimeout: 15_000, // this step gets 15 seconds
})

Pass an AbortSignal to cancel the flow from outside.

const controller = new AbortController();
const promise = flow.execute(input, deps, {
signal: controller.signal,
});
// Cancel from outside
controller.abort();

Inside step handlers, ctx.signal exposes a combined signal that fires when any of the three layers trigger. Pass it to async operations for true interruption.

flow.step('fetchData', async (ctx) => {
// Pass signal to fetch for real cancellation
const resp = await fetch(url, { signal: ctx.signal });
return { data: await resp.json() };
});

You can also check ctx.signal.aborted for cooperative cancellation in long-running loops:

flow.step('processItems', async (ctx) => {
for (const item of ctx.state.items) {
if (ctx.signal.aborted) break;
await processItem(item);
}
});

All three layers work together. The combined signal fires as soon as any one triggers.

const controller = new AbortController();
await flow.execute(input, deps, {
timeout: 30_000,
stepTimeout: 5_000,
signal: controller.signal,
});