# Why do my Web Worker results come back out of order even with a single queue?

**URL:** <https://forum.kirupa.com/t/why-do-my-web-worker-results-come-back-out-of-order-even-with-a-single-queue/681540>\
**Category:** web dev\
**Created:** [May 16, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-do-my-web-worker-results-come-back-out-of-order-even-with-a-single-queue/681540 "2026-05-16T07:00:13Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![WaffleFries](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/wafflefries/32/31185_2.png) [@WaffleFries](https://forum.kirupa.com/u/WaffleFries)\
**Post date:** [May 16, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-do-my-web-worker-results-come-back-out-of-order-even-with-a-single-queue/681540/1 "2026-05-16T07:00:13Z")

</div>

Yo folks, I’m wiring up a Web Worker to crunch a bunch of tasks and push results back to the UI, but I’m seeing stale results land after newer ones. I’m trying to keep the main thread snappy, but I also don’t want to leak memory by keeping every pending job forever.

```js
const worker = new Worker("worker.js");
let nextId = 0;
const pending = new Map();

export function runJob(payload) {
  const id = nextId++;
  worker.postMessage({ id, payload });
  return new Promise((resolve, reject) => {
    pending.set(id, { resolve, reject });
  });
}

worker.onmessage = (e) => {
  const { id, result } = e.data;
  const p = pending.get(id);
  if (!p) return; // sometimes this hits
  pending.delete(id);
  p.resolve(result);
};

```

What’s the cleanest pattern to handle backpressure + cancellation so old jobs can’t “win” and I don’t end up with a growing pending Map?

---

<div class="post-metadata">

**Author:** ![HariSeldon](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/hariseldon/32/31261_2.png) [@HariSeldon](https://forum.kirupa.com/u/HariSeldon)\
**Post date:** [May 17, 2026, 7:40am UTC](https://forum.kirupa.com/t/why-do-my-web-worker-results-come-back-out-of-order-even-with-a-single-queue/681540/2 "2026-05-17T07:40:37Z")

</div>

A single worker gives you FIFO _message handling_, not FIFO _completion_. The moment `worker.js` yields (awaits anything, uses `setTimeout`, does chunking, etc.), later jobs can finish first and `postMessage` back first.

What’s worked cleanly for me is treating “promise resolution” and “UI is allowed to apply this” as two different concerns. Keep your `pending` map so every `runJob()` promise settles, but gate UI updates with a “latest request” marker per UI surface (search box, chart, whatever): store `latestId = id` when you kick off work, and when results arrive you still resolve the promise, but you only apply to UI when `id === latestId`. Old jobs don’t “win”, and you don’t need to keep old results around.

For backpressure + cancellation, I’d keep it boring: cap in-flight work and explicitly cancel. If `pending.size` is over some limit, either enqueue on the main thread or reject fast (your call), but don’t let it grow forever. For cancellation, send `{ type: "cancel", id }` and have the worker check a `cancelled` set between chunks/before posting results; on the main thread, make sure you `pending.delete(id)` on success, error, and timeout/abort, otherwise you end up with a promise cemetery even if the UI ignores stale results.
