Skip to content

Loops & retry patterns

.loopTo(targetStageName) creates a back-edge to any earlier stage. The loop continues until a stage calls scope.$break(). This enables polling, pagination, retry, and iterative refinement patterns.

import { flowChart, FlowChartExecutor } from 'footprintjs';
const chart = flowChart<{ counter: number; target: number }>('Init', async (scope) => {
scope.counter = 0;
scope.target = 5;
}, 'init')
.addFunction('Increment', async (scope) => {
scope.counter += 1;
if (scope.counter >= scope.target) scope.$break();
}, 'increment')
.loopTo('Increment')
.build();
const executor = new FlowChartExecutor(chart);
executor.enableNarrative();
await executor.run();
// Narrative: "Looped back to Increment (pass 2)" ... "Looped back to Increment (pass 5)"
interface RetryState {
attempt: number;
maxAttempts: number;
result?: string;
}
const chart = flowChart<RetryState>('Init', async (scope) => {
scope.attempt = 0;
scope.maxAttempts = 3;
}, 'init')
.addFunction('CallAPI', async (scope) => {
scope.attempt += 1;
try {
// Simulate flaky API
if (Math.random() < 0.5) throw new Error('timeout');
scope.result = 'success';
scope.$break();
} catch {
if (scope.attempt >= scope.maxAttempts) {
scope.result = 'failed after retries';
scope.$break();
}
// Wait before retry
await new Promise((r) => setTimeout(r, scope.attempt * 100));
}
}, 'call-api')
.loopTo('CallAPI')
.build();
interface PaginationState {
page: number;
allItems: string[];
hasMore: boolean;
}
const chart = flowChart<PaginationState>('Init', async (scope) => {
scope.page = 0;
scope.allItems = [];
scope.hasMore = true;
}, 'init')
.addFunction('FetchPage', async (scope) => {
scope.page += 1;
const items = await fetchPage(scope.page); // your API call
scope.allItems = [...scope.allItems, ...items];
scope.hasMore = items.length > 0;
if (!scope.hasMore) scope.$break();
}, 'fetch-page')
.loopTo('FetchPage')
.build();

When loops run many iterations, the narrative grows. Use loop-aware strategies to control output:

import {
WindowedNarrativeFlowRecorder,
AdaptiveNarrativeFlowRecorder,
SilentNarrativeFlowRecorder,
} from 'footprintjs';
// Keep first 3 + last 2 iterations
executor.attachFlowRecorder(new WindowedNarrativeFlowRecorder(3, 2));
// Full for first 3, then every 5th
executor.attachFlowRecorder(new AdaptiveNarrativeFlowRecorder(3, 5));
// Summary only — "Looped 50 times"
executor.attachFlowRecorder(new SilentNarrativeFlowRecorder());