Skip to content

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:

  1. Inferencer sends images to multiple Docker containers
  2. Returns promises instead of waiting for results
  3. Promise Reader resolves all promises
  4. 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 → reads msg.promises
  • inference.promises → reads msg.inference.promises
  • async.results → reads msg.async.results

Input

Required Input Format

The specified field must contain an array of valid JavaScript promises:

javascript
msg.promises = [
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> }
];
return msg;

Example: From Inferencer

javascript
// 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

javascript
// Function node: Create custom async operations
msg.promises = [
  processImageAsync(image1),
  processImageAsync(image2),
  processImageAsync(image3)
];
return msg;

Processing Behavior

Step-by-Step Process

  1. Extract Promises

    • Locates promise array in specified field
    • Validates all elements are promises
    • Errors if validation fails
  2. Parallel Resolution

    • Uses Promise.all() for concurrent resolution
    • Waits for ALL promises to complete
    • Slowest promise determines total time
  3. Result Aggregation

    • Iterates through resolved values
    • Merges each result into message object
    • Uses deep merge for nested objects
    • Simple assignment for primitives and arrays
  4. Performance Tracking

    • Extracts performance data from each result
    • Finds earliest startTime
    • Finds latest endTime
    • Calculates total milliseconds
    • Stores in msg.performance[nodeName]
  5. Cleanup

    • Removes original promise array
    • Keeps all other message properties
    • Sends aggregated message to output

Merge Strategy

Simple values (overwrite):

javascript
// Promise 1 resolves: { confidence: 0.9 }
// Promise 2 resolves: { confidence: 0.95 }
// Result: msg.confidence = 0.95 (last wins)

Arrays (overwrite):

javascript
// Promise 1 resolves: { detections: [det1, det2] }
// Promise 2 resolves: { detections: [det3, det4] }
// Result: msg.detections = [det3, det4] (last wins)

Objects (deep merge):

javascript
// 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

javascript
{
  // 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

javascript
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:

javascript
msg.promises = [Promise1, Promise2, Promise3];

Promise Reader output:

javascript
msg.detections = [
  [{ class: "person", confidence: 0.95 }],
  [{ class: "car", confidence: 0.89 }],
  []
];

Example 2: Nested Field Path

Function node before Promise Reader:

javascript
msg.inference = {
  promises: [promise1, promise2],
  metadata: { timestamp: Date.now() }
};
return msg;

Promise Reader configuration:

  • Field to read: inference.promises

Output:

javascript
msg.inference = {
  // promises removed
  metadata: { timestamp: 1234567890 },
  // Results merged here
  results: [...]
};

Example 3: Batch Image Processing

javascript
// Function node: Create batch
const images = Array.from({ length: 10 }, (_, i) => loadImage(i));
msg.payload = images;
return msg;

Inferencer (Promise mode, 10 images):

javascript
msg.promises = [/* 10 promises */];

Promise Reader resolves all:

javascript
msg.results = [/* 10 results */];
msg.performance["promise-reader"].milliseconds; // Total time

Example 4: Custom Async Operations

javascript
// 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:

javascript
msg.processed = [result1, result2, ...];
msg.performance["promise-reader"] = {
  startTime: /* earliest */,
  endTime: /* latest */,
  milliseconds: /* total */
};

Example 5: Error Handling in Promises

javascript
// 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:

javascript
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:

javascript
// After Promise Reader 1
msg.detections = [/* detected objects */];

Function node:

javascript
// Extract regions for classification
msg.payload = extractRegions(msg.detections);
return msg;

Stage 2 - Classification:

javascript
// After Promise Reader 2
msg.classifications = [/* classified objects */];

Performance Considerations

Timing Behavior

Total time = slowest promise:

javascript
// 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 promises

Cause: Field is not an array

Behavior: Warns and passes message unchanged

Example:

javascript
msg.promises = "not an array";  // Warning triggered

Non-Promise Elements

Error:

All elements in msg.promises must be promises

Cause: Array contains non-promise values

Behavior: Errors and stops processing

Example:

javascript
msg.promises = [Promise1, "not a promise", Promise3];  // Error

Missing Field

Warning:

msg.promises must be an array of promises

Cause: Specified field doesn't exist

Behavior: Warns and passes message unchanged

Example:

javascript
// Field to read: promises
// But msg.promises is undefined

Promise 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:

javascript
// 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

  1. Wrap promises with error handling:
javascript
msg.promises = images.map(async (img) => {
  try {
    return await processImage(img);
  } catch (error) {
    return { error: error.message, success: false };
  }
});
  1. Validate before creating promises:
javascript
const validImages = images.filter(img => img && img.data);
msg.promises = validImages.map(img => process(img));
  1. Set timeouts:
javascript
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:

javascript
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:

javascript
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:

javascript
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

  1. Batch Size: Keep to 10-50 promises for optimal performance
  2. Error Handling: Always wrap promises with try-catch
  3. Timeouts: Set appropriate timeouts for all async operations
  4. Memory: Monitor memory usage with large batches
  5. Performance: Log timing data for optimization
  6. Validation: Check promise count before processing
  7. Documentation: Document custom field paths clearly

See Also