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

**URL:** <https://forum.kirupa.com/t/why-does-my-worker-keep-sending-results-after-i-cancel-the-job/681825>\
**Category:** web dev\
**Created:** [May 30, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-worker-keep-sending-results-after-i-cancel-the-job/681825 "2026-05-30T07:00:11Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![sarah\_connor](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/sarah_connor/32/31258_2.png) [@sarah\_connor](https://forum.kirupa.com/u/sarah_connor)\
**Post date:** [May 30, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-worker-keep-sending-results-after-i-cancel-the-job/681825/1 "2026-05-30T07:00:11Z")

</div>

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.

```js
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?

---

<div class="post-metadata">

**Author:** ![Yoshiii](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/yoshiii/32/31156_2.png) [@Yoshiii](https://forum.kirupa.com/u/Yoshiii)\
**Post date:** [May 31, 2026, 7:20am UTC](https://forum.kirupa.com/t/why-does-my-worker-keep-sending-results-after-i-cancel-the-job/681825/2 "2026-05-31T07:20:18Z")

</div>

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.
