ForEach Control Node
Overview
The ForEach control node is used to iterate over each element in an array, creating an independent sub-workflow environment for processing each element. As a pure control node, it focuses on loop control logic and does not perform result aggregation.
Node Configuration
Basic Properties
{
"id": "foreach-1",
"type": "control",
"config": {
"identifier": "foreach",
"title": "Array Loop Processing",
"description": "Process array data item by item"
}
}
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
data | Object/Array | - | The data to process — can be an array or an object containing an array |
iterableField | String | 'list' | The field name to extract the array from the data object |
maxIterations | Number | 1000 | Maximum iteration limit to prevent infinite loops |
continueOnError | Boolean | true | Whether to continue processing other items when a single item fails |
Output Ports
| Port Name | Description |
|---|---|
item | Connects to the sub-workflow, passing the current iteration item |
Data Input Methods
Method 1: Direct Array
When data itself is an array, ForEach iterates over it directly:
// Data generator returns
return [1, 2, 3, 4, 5];
// ForEach will iterate over each number
Method 2: Array Field Within an Object
When data is an object, ForEach extracts the array from the specified field:
// Data generator returns
return {
list: [
{ id: 1, name: "Product A" },
{ id: 2, name: "Product B" }
],
total: 2
};
// ForEach will iterate over the array in the list field
Method 3: Custom Field Name
Specify the field name using the iterableField parameter:
// Data generator returns
return {
products: [
{ id: 1, name: "Product A" },
{ id: 2, name: "Product B" }
]
};
// Set iterableField to "products"
Data retrieval logic:
- If
datais an array → iterate directly - If
datais an object → extract theiterableFieldfield (defaults tolist) - Otherwise → use an empty array
Iteration Context
ForEach provides rich context information for each iteration, accessible within the sub-workflow via context[nodeId]:
// Context structure
context['foreach-1'] = {
item: currentItem, // Current iteration item
_iteration_info: {
index: 0, // Current index (zero-based)
total: 5, // Total number of items
isFirst: true, // Whether this is the first item
isLast: false // Whether this is the last item
}
}
Usage Examples
Example 1: Numeric Array Processing
{
"nodes": {
"data-generator": {
"type": "operator",
"config": {
"operator": "jsexecutor",
"code": "return [10, 20, 30, 40, 50];"
}
},
"foreach-numbers": {
"type": "control",
"config": {
"identifier": "foreach"
}
},
"number-processor": {
"type": "operator",
"config": {
"operator": "jsexecutor",
"code": `
const number = context['foreach-numbers'].item;
const info = context['foreach-numbers']._iteration_info;
console.log(\`Processing number \${info.index + 1}: \${number}\`);
return {
original: number,
doubled: number * 2,
isLast: info.isLast
};
`
}
}
},
"edges": [
{
"source": "data-generator",
"target": "foreach-numbers",
"sourcePort": "result",
"targetPort": "data"
},
{
"source": "foreach-numbers",
"target": "number-processor",
"sourcePort": "item",
"targetPort": "parameters"
}
]
}
Example 2: Object Array Processing
{
"nodes": {
"user-generator": {
"type": "operator",
"config": {
"operator": "jsexecutor",
"code": `
return {
list: [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Charlie", age: 28 }
]
};
`
}
},
"foreach-users": {
"type": "control",
"config": {
"identifier": "foreach"
}
},
"user-processor": {
"type": "operator",
"config": {
"operator": "jsexecutor",
"code": `
const user = context['foreach-users'].item;
const info = context['foreach-users']._iteration_info;
return {
...user,
processOrder: info.index + 1,
isAdult: user.age >= 18,
isFirstUser: info.isFirst,
isLastUser: info.isLast
};
`
}
}
},
"edges": [
{
"source": "user-generator",
"target": "foreach-users",
"sourcePort": "result",
"targetPort": "data"
},
{
"source": "foreach-users",
"target": "user-processor",
"sourcePort": "item",
"targetPort": "parameters"
}
]
}
Example 3: Processing with Aggregation
{
"nodes": {
"score-generator": {
"type": "operator",
"config": {
"operator": "jsexecutor",
"code": "return [85, 92, 78, 96, 88];"
}
},
"foreach-scores": {
"type": "control",
"config": {
"identifier": "foreach"
}
},
"score-processor": {
"type": "operator",
"config": {
"operator": "jsexecutor",
"code": `
const score = context['foreach-scores'].item;
const info = context['foreach-scores']._iteration_info;
// Initialize the accumulator
if (!context._scoreStats) {
context._scoreStats = {
total: 0,
count: 0,
scores: []
};
}
// Accumulate statistics
context._scoreStats.total += score;
context._scoreStats.count++;
context._scoreStats.scores.push(score);
// If this is the last item, calculate the average
if (info.isLast) {
const average = context._scoreStats.total / context._scoreStats.count;
return {
average: Math.round(average * 100) / 100,
total: context._scoreStats.total,
count: context._scoreStats.count,
allScores: context._scoreStats.scores
};
}
return { currentScore: score, processed: info.index + 1 };
`
}
}
},
"edges": [
{
"source": "score-generator",
"target": "foreach-scores",
"sourcePort": "result",
"targetPort": "data"
},
{
"source": "foreach-scores",
"target": "score-processor",
"sourcePort": "item",
"targetPort": "parameters"
}
]
}
Error Handling
Default Behavior (continueOnError = true)
// When a single item fails, continue processing other items
const continueOnError = inputs.continueOnError !== false; // Defaults to true
if (continueOnError) {
console.warn('Iteration failed, continuing to next iteration');
results.push(null); // Failed iterations are represented as null
}
Strict Mode (continueOnError = false)
// Any failure stops the entire loop
if (!continueOnError) {
throw error; // Throw exception, stop execution
}
Execution Characteristics
Sequential Execution
ForEach uses sequential execution mode, processing array elements one by one:
- Waits for the current iteration to complete before starting the next
- Avoids concurrent resource contention
- Guarantees processing order
Execution Metrics
ForEach outputs detailed execution statistics:
// Console output example
ForEach node execution completed {
nodeId: 'foreach-scores',
totalIterations: 5,
successCount: 5,
errorCount: 0,
executionTime: '125ms'
}
Safety Limits
- Maximum iterations: Defaults to 1000 to prevent infinite loops
- Data conversion: Automatically handles non-array data
null/undefined→ empty array[]- Object →
Object.values(object) - Other types →
[value]
Common Patterns
Pattern 1: Accumulation
// Implement accumulation in the sub-workflow
const accumulator = `
const item = context['foreach-1'].item;
const info = context['foreach-1']._iteration_info;
if (!context._sum) context._sum = 0;
context._sum += item;
if (info.isLast) {
return { totalSum: context._sum };
}
return { currentSum: context._sum };
`;
Pattern 2: Conditional Filtering
// Conditional processing
const filter = `
const item = context['foreach-1'].item;
const info = context['foreach-1']._iteration_info;
if (!context._results) context._results = [];
if (item > 50) { // Condition check
context._results.push(item);
}
if (info.isLast) {
return { filteredResults: context._results };
}
return null;
`;
Pattern 3: Batch Processing
// Process in batches
const batchProcessor = `
const item = context['foreach-1'].item;
const info = context['foreach-1']._iteration_info;
const batchSize = 3;
if (!context._batch) context._batch = [];
context._batch.push(item);
// Output when batch size is reached or on the last item
if (context._batch.length === batchSize || info.isLast) {
const batch = [...context._batch];
context._batch = []; // Reset
return { batch: batch, batchNumber: Math.ceil((info.index + 1) / batchSize) };
}
return null;
`;
Best Practices
1. Data Input Recommendations
// ✅ Recommended: Direct array (simplest)
return [1, 2, 3, 4, 5];
// ✅ Recommended: Structured object (more flexible)
return {
list: [1, 2, 3, 4, 5],
metadata: { source: 'api', timestamp: Date.now() }
};
2. Context Usage Tips
// Leverage iteration info
const processor = `
const item = context['foreach-1'].item;
const info = context['foreach-1']._iteration_info;
// Initialize on first item
if (info.isFirst) {
context._initialized = true;
}
// Clean up on last item
if (info.isLast) {
return { completed: true, processed: info.total };
}
return { item: item, position: info.index + 1 };
`;
3. Error Handling Strategy
// Choose error handling mode based on requirements
{
"continueOnError": true // Tolerant mode, suitable for data cleansing
}
{
"continueOnError": false // Strict mode, suitable for critical business logic
}
ForEach is a pure control node and does not return aggregated results. All business logic and data aggregation should be performed within the nodes of the sub-workflow.
Related Documentation
- Workflow Overview — Workflow engine fundamentals
- Context Management — Workflow context and data passing
- If-Else Condition Node — Conditional control node
- Switch Branch Node — Multi-branch control node