Skip to content

Latest commit

 

History

History
106 lines (77 loc) · 3.83 KB

File metadata and controls

106 lines (77 loc) · 3.83 KB

Object Construction: C++ vs Worker Thread

The Question

Why can't we construct a complete JS object in C++ or worker thread so it arrives fully-formed on the main thread with zero work needed?

Answer: We CAN and DO!

✅ Worker Thread (Already Doing This!)

In Worker Thread:

const parsed = JSON.parse(candidate);  // Complete POJSO created here!
batch.push(parsed);  // Full object with all nested properties
parentPort!.postMessage({ type: 'data', batch });  // Structured cloning

On Main Thread:

if (msg.type === 'data') {
  // msg.batch contains COMPLETE objects - fully constructed!
  // No JSON.parse() needed, no object construction needed
  this.pending.push(...msg.batch);  // Just use them!
}

Result: Objects arrive 100% complete via structured cloning. Zero work on main thread!

⚠️ Native C++ Parser (The Limitation)

In C++ Background Thread:

JValue parsed;  // C++ struct, not a JS object
parse_json(candidate, parsed);  // Parse into C++ data structure
// Can't create N-API values here - need main thread's napi_env!

On Main Thread (TSFN callback):

// Must convert C++ struct to JS object HERE (on main thread)
napi_value parsed_val = jvalue_to_js(env, it.value);  // Construction happens here!
// This creates the full object structure using N-API calls

Why? N-API values (JS objects) can only be created on the main thread because they need the correct napi_env which is tied to the V8 isolate on the main thread.

Result: Object construction happens on main thread = overhead!

✅ Native-Optimized (Buffers)

In C++ Background Thread:

// Store raw JSON string
item.raw = candidate;
item.external_data = std::make_unique<uint8_t[]>(candidate.size());
// Pass as Buffer (zero-copy)

On Main Thread:

// Receive Buffer, parse to complete object
const parsed = JSON.parse(buf.toString('utf8'));  // Complete object created here

Result: Main thread does JSON.parse() but gets complete object. Still some work, but uses V8's optimized parser.

The Ideal Solution

What We Want:

  1. Parse JSON in background thread → Create complete JS object
  2. Pass complete object to main thread → Zero work on main thread
  3. Main thread just receives and uses → Perfect!

What We Have:

Implementation Object Construction Main Thread Work
Worker ✅ Complete in worker ✅ Zero (just receive)
Native C++ ❌ C++ struct → JS on main ⚠️ Full construction
Native Buffers ⚠️ Parse on main ⚠️ JSON.parse() on main

Why Worker is Best for Complete Objects

Worker thread:

  • Uses V8's JSON.parse() → Creates complete JS object with all properties
  • postMessage with structured cloning → Transfers complete object graph
  • Main thread receives → Fully-formed object, zero construction needed

Native C++:

  • Parses in C++ → Creates C++ struct
  • Must convert to JS on main thread → Construction overhead
  • Main thread does work → Not ideal

The Surprise

You're right to be surprised! The worker implementation DOES construct complete objects and passes them via structured cloning. The objects arrive on the main thread fully constructed - no additional work needed!

The native C++ parser has the limitation that N-API values must be created on the main thread, so it can't pass complete objects directly - it has to construct them on the main thread.

Conclusion

  • Worker: ✅ Complete objects, zero main thread work
  • Native C++: ⚠️ Must construct on main thread
  • Native Buffers: ⚠️ Must parse on main thread

The worker approach is actually doing exactly what you want - complete objects with zero construction work on the main thread!