Engineering Journal
Pdf Processor
Pdf Processor

Error Fix: The Web Worker Payload Misnomer That Broke Batch Extractions

2026-08-06

TLDR

After building a Web Worker pool for off-thread multi-document ingestion, batch file uploads reported COMPLETED status but failed to render any extracted content when focused. The main thread WorkerPool dispatched message payloads with key buffer, whereas the Web Worker listener destructured bytes. Because bytes evaluated to undefined, TextDecoder and document parsers received invalid buffers and returned empty HTML strings ("").
LayerDeclared Property KeyEvaluated ValueExtraction Result
workerPool.js (Line 53)buffer: arrayBufferValid ArrayBufferSent across worker boundary
pipelineWorker.js (Line 34)const { bytes } = e.dataundefinedFails: Cannot decode undefined
Fixed pipelineWorker.jsbuffer ? new Uint8Array(buffer) : nullValid Uint8ArraySuccess: Valid HTML extracted

Technical defect analysis

When batch items were processed by WorkerPool, postMessage transferred an ArrayBuffer under the property name buffer:

// workerPool.js (Main Thread)
workerEntry.worker.postMessage({
    type: 'process_batch_item',
    id: task.item.id,
    format: task.item.format,
    buffer: arrayBuffer  // Property name is 'buffer'
}, [arrayBuffer]);

Inside the background worker (pipelineWorker.js), the event handler attempted to destructure bytes:

// DEFECTIVE: pipelineWorker.js
self.onmessage = async (e) => {
    if (e.data.type === 'process_batch_item') {
        const { id, bytes, format } = e.data; // Fails: e.data.bytes is undefined!

let extractedHTML = ''; if (format === 'html') { // TextDecoder(undefined) returns empty string without throwing! extractedHTML = new TextDecoder().decode(bytes); }

self.postMessage({ type: 'batch_item_complete', id, result: { extractedHTML } }); } };

Because TextDecoder and PDF parsers did not throw fatal exceptions on undefined or null inputs, execution reached self.postMessage successfully. The batch item status was updated to completed, but contained an empty string ("") for extractedHTML.


Remediation: universal payload extraction

Update pipelineWorker.js to extract bytes from either rawBytes or buffer:

// REFACTORED: pipelineWorker.js
self.onmessage = async (e) => {
    if (e.data.type === 'process_batch_item') {
        const { id, buffer, bytes: rawBytes, fileName, format } = e.data;
        
        // Coerce both buffer and rawBytes into a valid Uint8Array
        const bytes = rawBytes ? new Uint8Array(rawBytes) : (buffer ? new Uint8Array(buffer) : null);

try { let extractedHTML = ''; let extractedText = ''; let gxDoc = null;

if (bytes && (format === 'html' || format === 'md')) { const text = new TextDecoder().decode(bytes); extractedText = text; extractedHTML = format === 'md' ? <pre>${text}</pre> : text; }

self.postMessage({ type: 'batch_item_complete', id, result: { gxDoc, extractedHTML, extractedText, bytes: bytes ? bytes.buffer : null } }, bytes ? [bytes.buffer] : []); } catch (err) { self.postMessage({ type: 'batch_item_error', id, error: err.message }); } } };

Why this fix works

  1. Fallback Coercion: Handlers gracefully convert ArrayBuffer inputs (buffer) or byte arrays (rawBytes) into a valid Uint8Array.
  2. Transferable Return: The worker transfers bytes.buffer back to the main thread, allowing PDF canvas renderers to draw cached page data instantly.
Rule of thumb: When passing objects across Web Worker boundaries, handle property key aliases explicitly to prevent silent undefined serialization errors.
Read this post in the full Engineering Journal →