Skip to main content

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

ParameterTypeDefaultDescription
dataObject/Array-The data to process — can be an array or an object containing an array
iterableFieldString'list'The field name to extract the array from the data object
maxIterationsNumber1000Maximum iteration limit to prevent infinite loops
continueOnErrorBooleantrueWhether to continue processing other items when a single item fails

Output Ports

Port NameDescription
itemConnects 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:

  1. If data is an array → iterate directly
  2. If data is an object → extract the iterableField field (defaults to list)
  3. 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
}
Important Note

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.