Why does my worker keep sending results after I cancel the job?

What’s up everyone? I’m trying to offload some heavy parsing to a Web Worker while keeping the UI responsive, but I’m hitting a nasty failure mode where “canceled” work still posts results back later and overwrites newer state.

const worker = new Worker("/parseWorker.js");
let currentJobId = 0;

export function parseAsync(text) {
  const jobId = ++currentJobId;
  worker.postMessage({ jobId, text });

  return new Promise((resolve) => {
    worker.addEventListener("message", (e) => {
      if (e.data.jobId !== jobId) return; // ignore stale
      resolve(e.data.result);
    });
  });
}

export function cancelAll() {
  currentJobId++; // invalidate
}

Am I leaking listeners / creating a race here, and what’s the pragmatic pattern to do cancellation + backpressure with a single long-lived worker without stale results clobbering the UI state?

1 Like

the listener leak is the real problem here. every parseAsync() call adds another "message" handler, and none of them go away, so old calls hang around and can still resolve later.

i’d switch to one shared worker.onmessage and route by jobId with a Map of resolvers. that keeps the worker simple and stops the “one listener per request” pileup.

canceling here only invalidates the result on the main thread. the worker still does the work unless it cooperates, so for actual cancellation you need a cancel flag in the worker or chunked parsing that checks between chunks.