Promise Reader Node
Overview
The Promise Reader node resolves arrays of JavaScript promises and aggregates their results into the message object. It's essential for handling asynchronous operations from nodes that use promise-based processing, such as the Inferencer node with concurrent inference requests.
Key Features
- Promise resolution: Waits for all promises using
Promise.all() - Parallel processing: Resolves all promises concurrently
- Result aggregation: Merges resolved data into message
- Performance tracking: Aggregates timing metrics from all promises
- Deep merging: Intelligently combines nested objects
- Automatic cleanup: Removes promise array after resolution
- Error handling: Graceful handling of rejected promises
- Flexible field paths: Support for nested message properties
Use Cases
Primary Use Case: Inferencer Promise Mode
When the Inferencer node operates in promise mode:
- Inferencer sends images to multiple Docker containers
- Returns promises instead of waiting for results
- Promise Reader resolves all promises
- Aggregated results flow to next node
Other Use Cases
- Batch processing with async operations
- Multi-server load balancing results
- Concurrent API calls aggregation
- Parallel image processing pipelines
Configuration
Properties
Name
- Type: String
- Optional: Yes
- Default: "promise-reader"
- Description: Display name for the node instance
Field to Read
- Type: Message property path
- Default:
promises - Required: Yes
- Description: Message field containing the promise array
- Supports: Dot notation for nested fields
Examples:
promises→ readsmsg.promisesinference.promises→ readsmsg.inference.promisesasync.results→ readsmsg.async.results
Input
Required Input Format
The specified field must contain an array of valid JavaScript promises:
msg.promises = [
Promise { <pending> },
Promise { <pending> },
Promise { <pending> }
];
return msg;Example: From Inferencer
// Inferencer node with Promise mode enabled
// Processes 3 images, returns 3 promises
msg.payload = [image1, image2, image3];
// After inferencer:
// msg.promises = [Promise, Promise, Promise]Example: Custom Promises
// Function node: Create custom async operations
msg.promises = [
processImageAsync(image1),
processImageAsync(image2),
processImageAsync(image3)
];
return msg;Processing Behavior
Step-by-Step Process
Extract Promises
- Locates promise array in specified field
- Validates all elements are promises
- Errors if validation fails
Parallel Resolution
- Uses
Promise.all()for concurrent resolution - Waits for ALL promises to complete
- Slowest promise determines total time
- Uses
Result Aggregation
- Iterates through resolved values
- Merges each result into message object
- Uses deep merge for nested objects
- Simple assignment for primitives and arrays
Performance Tracking
- Extracts performance data from each result
- Finds earliest startTime
- Finds latest endTime
- Calculates total milliseconds
- Stores in
msg.performance[nodeName]
Cleanup
- Removes original promise array
- Keeps all other message properties
- Sends aggregated message to output
Merge Strategy
Simple values (overwrite):
// Promise 1 resolves: { confidence: 0.9 }
// Promise 2 resolves: { confidence: 0.95 }
// Result: msg.confidence = 0.95 (last wins)Arrays (overwrite):
// Promise 1 resolves: { detections: [det1, det2] }
// Promise 2 resolves: { detections: [det3, det4] }
// Result: msg.detections = [det3, det4] (last wins)Objects (deep merge):
// Promise 1 resolves: { config: { threshold: 0.5 } }
// Promise 2 resolves: { config: { maxDet: 100 } }
// Result: msg.config = { threshold: 0.5, maxDet: 100 } (merged)Output
Aggregated Results
{
// Original message properties preserved
payload: [image1, image2, image3],
topic: "inference",
// Merged results from resolved promises
detections: [
[det1, det2], // From promise 1
[det3], // From promise 2
[] // From promise 3
],
// Performance metrics
performance: {
"promise-reader": {
startTime: Date(2024-01-15T10:00:00.000Z),
endTime: Date(2024-01-15T10:00:01.500Z),
milliseconds: 1500
},
// Other node performance data preserved
"inferencer": {
// Individual inference times
}
}
// Original msg.promises removed
}Performance Data Structure
msg.performance["promise-reader"] = {
startTime: Date, // Earliest start from all promises
endTime: Date, // Latest end from all promises
milliseconds: Number // Total elapsed time
};Usage Examples
Example 1: Basic Inferencer Integration
[Camera] → [Inferencer: Promise Mode] → [Promise Reader] → [Display Results]Inferencer output:
msg.promises = [Promise1, Promise2, Promise3];Promise Reader output:
msg.detections = [
[{ class: "person", confidence: 0.95 }],
[{ class: "car", confidence: 0.89 }],
[]
];Example 2: Nested Field Path
Function node before Promise Reader:
msg.inference = {
promises: [promise1, promise2],
metadata: { timestamp: Date.now() }
};
return msg;Promise Reader configuration:
- Field to read:
inference.promises
Output:
msg.inference = {
// promises removed
metadata: { timestamp: 1234567890 },
// Results merged here
results: [...]
};Example 3: Batch Image Processing
// Function node: Create batch
const images = Array.from({ length: 10 }, (_, i) => loadImage(i));
msg.payload = images;
return msg;Inferencer (Promise mode, 10 images):
msg.promises = [/* 10 promises */];Promise Reader resolves all:
msg.results = [/* 10 results */];
msg.performance["promise-reader"].milliseconds; // Total timeExample 4: Custom Async Operations
// Function node: Custom promises
async function processImage(img) {
const processed = await heavyComputation(img);
return {
processed: processed,
performance: {
startTime: startTime,
endTime: new Date()
}
};
}
msg.promises = images.map(img => processImage(img));
return msg;Promise Reader aggregates:
msg.processed = [result1, result2, ...];
msg.performance["promise-reader"] = {
startTime: /* earliest */,
endTime: /* latest */,
milliseconds: /* total */
};Example 5: Error Handling in Promises
// Function node: Create promises with potential failures
msg.promises = images.map(async (img) => {
try {
return await processImage(img);
} catch (error) {
return { error: error.message, success: false };
}
});
return msg;Promise Reader output:
msg.results = [
{ success: true, data: [...] },
{ error: "Processing failed", success: false },
{ success: true, data: [...] }
];Example 6: Multi-Stage Processing
[Load Images]
→ [Inferencer 1: Detection (Promise)]
→ [Promise Reader 1]
→ [Function: Extract Regions]
→ [Inferencer 2: Classification (Promise)]
→ [Promise Reader 2]
→ [Output]Stage 1 - Detection:
// After Promise Reader 1
msg.detections = [/* detected objects */];Function node:
// Extract regions for classification
msg.payload = extractRegions(msg.detections);
return msg;Stage 2 - Classification:
// After Promise Reader 2
msg.classifications = [/* classified objects */];Performance Considerations
Timing Behavior
Total time = slowest promise:
// Promise 1: 100ms
// Promise 2: 150ms
// Promise 3: 80ms
// Total: 150ms (not 330ms)Concurrency Benefits
With Promise Mode:
- 10 images × 100ms = 1000ms total (parallel)
Without Promise Mode:
- 10 images × 100ms = 1000ms total (sequential)
Benefit: Same time but non-blocking Node-RED flow
Memory Usage
Considerations:
- All promises held in memory
- All results stored before merging
- Large batches may consume significant memory
Recommendations:
- Limit batch size (10-50 typical)
- Monitor Node-RED memory usage
- Process in chunks for very large batches
Error Handling
Validation Errors
Invalid Input Type
Error:
msg.promises must be an array of promisesCause: Field is not an array
Behavior: Warns and passes message unchanged
Example:
msg.promises = "not an array"; // Warning triggeredNon-Promise Elements
Error:
All elements in msg.promises must be promisesCause: Array contains non-promise values
Behavior: Errors and stops processing
Example:
msg.promises = [Promise1, "not a promise", Promise3]; // ErrorMissing Field
Warning:
msg.promises must be an array of promisesCause: Specified field doesn't exist
Behavior: Warns and passes message unchanged
Example:
// Field to read: promises
// But msg.promises is undefinedPromise Rejection Handling
Behavior:
- Uses
Promise.all()- any rejection fails all - Rejection logged as warning
- Message not sent to output
- Flow interrupted at this node
Handling rejected promises:
// Function node: Wrap promises to never reject
msg.promises = promises.map(p =>
p.catch(error => ({ error: error.message }))
);
return msg;Best Practices for Error Handling
- Wrap promises with error handling:
msg.promises = images.map(async (img) => {
try {
return await processImage(img);
} catch (error) {
return { error: error.message, success: false };
}
});- Validate before creating promises:
const validImages = images.filter(img => img && img.data);
msg.promises = validImages.map(img => process(img));- Set timeouts:
const timeoutPromise = (promise, ms) => {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
)
]);
};
msg.promises = images.map(img =>
timeoutPromise(processImage(img), 5000)
);Integration Patterns
Pattern 1: Standard Inferencer Flow
[Camera] → [Inferencer (Promise)] → [Promise Reader] → [Filter] → [Database]Usage: Production inference pipeline
Pattern 2: Conditional Processing
[Load] → [Switch]
├→ [Fast Path: No Promise]
└→ [Slow Path: Promise] → [Promise Reader]Usage: Route based on batch size or complexity
Pattern 3: Parallel Pipelines
[Split]
├→ [Inferencer 1 (Promise)] → [Promise Reader 1] ┐
└→ [Inferencer 2 (Promise)] → [Promise Reader 2] ┴→ [Join]Usage: Multiple models on same images
Pattern 4: Iterative Processing
[Batch] → [Inferencer (Promise)] → [Promise Reader] → [Function: Check]
↑ ↓
└──────────── [Retry Failed] ←────────────────┘Usage: Retry failed inferences
Debugging
Enable Debug Logging
Function node before Promise Reader:
node.warn(`Promises count: ${msg.promises.length}`);
msg.promises.forEach((p, i) => {
p.then(result => node.warn(`Promise ${i} resolved: ${JSON.stringify(result)}`));
});
return msg;Inspect Performance
Function node after Promise Reader:
const perf = msg.performance["promise-reader"];
node.warn(`Total time: ${perf.milliseconds}ms`);
node.warn(`Start: ${perf.startTime}`);
node.warn(`End: ${perf.endTime}`);
return msg;Check Merge Results
Function node after Promise Reader:
node.warn(`Message keys: ${Object.keys(msg).join(', ')}`);
node.warn(`Promises removed: ${!msg.promises}`);
return msg;Troubleshooting
No Output from Node
Possible causes:
- Promise rejected (check logs)
- Field path incorrect
- No promises in array
Solutions:
- Check Node-RED debug panel
- Verify field path matches message structure
- Add error handling to promises
Incomplete Results
Possible causes:
- Deep merge behavior unexpected
- Some promises return undefined
Solutions:
- Review merge strategy
- Ensure all promises return objects
- Use arrays for collection results
High Memory Usage
Possible causes:
- Too many promises
- Large result objects
Solutions:
- Reduce batch size
- Process in chunks
- Clear unused message properties
Slow Processing
Possible causes:
- One slow promise delays all
- Too many concurrent operations
Solutions:
- Set promise timeouts
- Reduce batch size
- Optimize upstream operations
Best Practices
- Batch Size: Keep to 10-50 promises for optimal performance
- Error Handling: Always wrap promises with try-catch
- Timeouts: Set appropriate timeouts for all async operations
- Memory: Monitor memory usage with large batches
- Performance: Log timing data for optimization
- Validation: Check promise count before processing
- Documentation: Document custom field paths clearly
See Also
- Inferencer Node - Primary source of promises
- OCR Inferencer Node - OCR with promise mode
- Vision Platform Overview - Complete platform documentation
- JavaScript Promises - MDN documentation