# Web Worker in JavaScript: come funzionano, quando (e come) usarli

> Cos'è il main thread blocking, come creare un Web Worker per spostare calcoli pesanti fuori dalla UI, e quando invece non serve affatto.

- Autore / Author: Diego Betto (https://diegobetto.com/about/)
- Pubblicato / Published: 2026-09-01
- URL: https://diegobetto.com/web-worker-javascript/
- Tag: javascript, performance

JavaScript, nel browser, gira su un **thread unico**. Lo stesso thread che esegue il tuo codice disegna anche l'interfaccia, gestisce i click, fa scorrere la pagina. Finché il thread è libero, tutto sembra reattivo. Nel momento in cui gli dai un calcolo che richiede 300 millisecondi — parsare un JSON enorme, elaborare un'immagine, ordinare centinaia di migliaia di righe — quel thread smette di rispondere a qualunque altra cosa per tutta la durata del calcolo. Non un rallentamento: un blocco completo. Un click durante quei 300ms viene semplicemente ignorato finché il thread non torna libero.

Questo è il **main thread blocking**, ed è la causa più comune dietro un'interfaccia che sembra "impantanata" senza un errore evidente in console.

## Cosa fa davvero un Web Worker

Un **Web Worker** esegue JavaScript su un thread separato, completamente isolato da quello principale. Non blocca la UI perché semplicemente non condivide il suo thread — gira in parallelo.

```javascript
// worker.js — gira su un thread separato
self.onmessage = (e) => {
  const risultato = calcoloPesante(e.data);
  self.postMessage(risultato);
};

function calcoloPesante(numeri) {
  return numeri.reduce((acc, n) => acc + fattoriale(n), 0);
}

function fattoriale(n) {
  return n <= 1 ? 1 : n * fattoriale(n - 1);
}
```

```javascript
// main.js — il thread principale, quello che disegna la UI
const worker = new Worker("worker.js");

worker.postMessage([50000, 60000, 70000]);

worker.onmessage = (e) => {
  console.log("Risultato:", e.data);
};
```

Il thread principale resta libero per tutta la durata del calcolo: l'interfaccia continua a rispondere ai click, lo scroll resta fluido, mentre il worker macina il risultato in background.

> Un Worker non ha accesso al DOM — è il limite più importante da capire subito: dentro un Worker
> non esistono `document`, `window` né alcun elemento della pagina. Il Worker può solo ricevere
> dati, elaborarli, e restituire dati — non può leggere né modificare l'interfaccia direttamente.
> Ogni comunicazione passa da `postMessage`/`onmessage`, ed è per design: se un Worker potesse
> toccare il DOM, perderesti la garanzia di isolamento che lo rende sicuro da usare in parallelo.

## Il costo nascosto: la copia dei dati

`postMessage` non condivide memoria tra i due thread — per default **copia** i dati passati (con l'algoritmo di structured clone). Per oggetti piccoli è irrilevante. Per un array di alcuni milioni di numeri, quella copia stessa può costare tempo non trascurabile, vanificando parte del beneficio.

La soluzione per dati grandi è `Transferable`: invece di copiare un `ArrayBuffer`, lo **trasferisci** — la proprietà passa al Worker e il thread principale perde l'accesso a quella memoria, senza che nessuna copia avvenga:

```javascript
const buffer = new ArrayBuffer(1024 * 1024 * 50); // 50MB
worker.postMessage(buffer, [buffer]); // trasferito, non copiato — istantaneo
```

Dopo il trasferimento, `buffer` nel thread principale diventa inutilizzabile (`byteLength` torna 0) — è un vero passaggio di proprietà, non una condivisione.

## Quando NON serve un Worker

Questa è la parte che la maggior parte delle guide salta, ed è quella che conta di più nella pratica: **la maggioranza dei casi di UI lenta non è causata da un singolo calcolo pesante**, ma da troppo lavoro DOM ripetuto — un `querySelectorAll` dentro un loop, un re-render React scatenato da un evento `scroll` senza throttle, un layout thrashing (leggere e scrivere proprietà di layout alternate in un ciclo). Un Worker non aiuta in nessuno di questi casi, perché il problema non è mai stato "troppo calcolo JavaScript puro" — è lavoro DOM che, per definizione, deve stare sul thread principale, Worker o no.

> Prima misura, poi ottimizza — non il contrario. Prima di introdurre un Worker, apri il profiler
> delle DevTools e guarda dove va davvero il tempo durante il blocco. Se il tempo è nel calcolo puro
> (un ciclo, un parsing, una trasformazione dati), un Worker è la risposta giusta. Se il tempo è in
> "Recalculate Style" o "Layout", il problema è nel modo in cui il codice tocca il DOM — spesso
> risolvibile con [debounce o throttle](https://diegobetto.com/debounce-throttle-javascript/) sull'handler che
> scatena il lavoro, non con un thread in più.

## Un pattern pratico: il Worker come coda di lavoro

Per calcoli ricorrenti (non un singolo `postMessage`/`onmessage`), è comodo avvolgere il Worker in una piccola interfaccia a Promise:

```javascript
class WorkerPool {
  #worker;
  #pending = new Map();
  #nextId = 0;

  constructor(scriptUrl) {
    this.#worker = new Worker(scriptUrl);
    this.#worker.onmessage = ({ data }) => {
      this.#pending.get(data.id)?.resolve(data.result);
      this.#pending.delete(data.id);
    };
  }

  run(payload) {
    const id = this.#nextId++;
    return new Promise((resolve) => {
      this.#pending.set(id, { resolve });
      this.#worker.postMessage({ id, payload });
    });
  }
}

const pool = new WorkerPool("worker.js");
const risultato = await pool.run([50000, 60000, 70000]);
```

Così il resto del codice tratta il Worker come una normale funzione asincrona, senza gestire manualmente l'accoppiamento tra richieste e risposte.

## In sintesi

Un Web Worker vale il costo di implementazione quando il collo di bottiglia è calcolo JavaScript puro e prolungato — parsing di payload grandi, elaborazione di immagini/audio lato client, algoritmi che richiedono decine o centinaia di millisecondi. Per capire se quel tempo di blocco sta davvero danneggiando l'esperienza utente, vale la pena leggere anche l'articolo sui [Core Web Vitals](https://diegobetto.com/core-web-vitals-lcp-cls-inp/), dove l'INP misura esattamente questo: quanto tempo passa tra un'interazione dell'utente e la risposta visibile dell'interfaccia.
