voltar aos artigosweb platform
$cd~/artigos/web-workers-javascript-tarefas-segundo-plano
Web Platform
série 33 Conceitos JS·parte 16 de 38

Web Workers em JavaScript: Executando Tarefas em Segundo Plano sem Travar a Interface

12 minVanderlei José Alamino

Ao aprender JavaScript, ouvimos constantemente: JavaScript é single-threaded. Mas então surge uma dúvida: se existe apenas uma thread, como alguns sites conseguem processar imagens, vídeos ou grandes quantidades de dados sem congelar a página?

A resposta está em uma API do navegador chamada Web Workers.

snippetJavaScript
// Main thread — interface continua responsivaconst worker = new Worker('./worker.js');worker.postMessage(1_000_000_000);worker.onmessage = (event) => {  console.log('Resultado:', event.data);};

Neste guia da série 33 Conceitos JS — projeto open source criado por Leonardo Maldonado — você vai aprender:

  • Por que a thread principal congela com tarefas pesadas
  • O que são Web Workers e quando usá-los (e quando não)
  • A diferença entre async/await e paralelismo real
  • Como se comunicar com Workers via postMessage()
  • Por que Workers não podem acessar o DOM

Pré-requisitos: Familiaridade com o event loop e com HTTP/Fetch. Se você já entende que fetch não bloqueia a thread, verá por que Workers são necessários para trabalho CPU-intensivo.

web-workers-javascript-tarefas-segundo-plano.meta.jsonjson

// metadados do artigo

{
  "tag": "web platform",
  "readTime": "12 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 16,
    "total": 38
  }
}
abrir fonte original
web-workers-javascript-tarefas-segundo-plano.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

O Problema: A Thread Principal Faz Tudo

A Main Thread (thread principal) é responsável por executar JavaScript, atualizar o DOM, processar eventos, renderizar a interface e responder aos cliques do usuário — tudo ao mesmo tempo, numa única fila.

Imagine esta função:

snippetJavaScript
function calculoPesado() {  const inicio = Date.now();   while (Date.now() - inicio < 5000) {    // trabalho pesado  }} calculoPesado();

Durante cinco segundos:

  • A página congela
  • Botões param de responder
  • Animações travam
  • O scroll deixa de funcionar

O usuário tem a sensação de que o site quebrou. Esse é o custo de executar trabalho CPU-intensivo na mesma thread que cuida da interface.

02

seção

Analogia: Um Restaurante

Imagine uma cozinha com apenas um cozinheiro. Ele precisa atender clientes, preparar pratos, limpar utensílios e organizar pedidos. Se chegar um prato extremamente demorado, todo o restaurante para.

Agora imagine contratar um auxiliar. Enquanto o cozinheiro principal continua atendendo os clientes, o auxiliar faz o trabalho pesado nos bastidores.

snippettext
┌─────────────────────────────────────────────────────────────┐│  RESTAURANTE = NAVEGADOR                                    ││                                                             ││  Cozinheiro principal (Main Thread)                         ││    → atende clientes, atualiza cardápio, interface fluida   ││                                                             ││  Auxiliar (Worker)                                          ││    → prepara pratos demorados em paralelo                   │└─────────────────────────────────────────────────────────────┘

Essa é exatamente a ideia dos Web Workers: delegar processamento pesado para uma thread separada enquanto a Main Thread mantém a experiência do usuário.

03

seção

O que é um Web Worker?

Um Web Worker é um arquivo JavaScript executado em uma thread separada. Ele possui:

  • Seu próprio event loop
  • Sua própria memória
  • Seu próprio contexto global (self)

E executa em paralelo com a thread principal — em hardware com múltiplos núcleos, isso pode significar uso real de CPU em paralelo.

snippettext
Main Thread                    Worker Thread     │                               │     │  postMessage(dados)           │     ├──────────────────────────────►│     │                               │  processamento pesado     │  onmessage(resultado)         │     │◄──────────────────────────────┤     │                               │  Atualiza DOM                  Calcula

Importante: JavaScript continua sendo single-threaded em cada thread. Workers não tornam o JS multi-threaded dentro de uma mesma thread — eles criam threads adicionais no navegador.

04

seção

Quando Usar Web Workers?

Workers são ideais para tarefas intensivas em CPU — trabalho que manteria a Main Thread ocupada por muito tempo:

✅ Processamento de imagens (redimensionar, aplicar filtros) ✅ Cálculos matemáticos complexos ✅ Filtros e ordenação em listas muito grandes ✅ Análise de arquivos (CSV, logs) ✅ Geração de PDFs ✅ Compressão/descompressão de dados ✅ Manipulação de frames de vídeo

Quando NÃO usar

Para tarefas que já são naturalmente assíncronas e não bloqueiam a thread principal:

  • fetch() e requisições HTTP
  • setTimeout / setInterval
  • WebSocket
  • Leitura de APIs do navegador (IndexedDB, etc.)

Essas operações delegam o trabalho às Web APIs do navegador — o padrão que você viu no event loop. Workers são para quando o próprio JavaScript precisa fazer muito cálculo.

05

seção

Async Não É Paralelismo

Muitas pessoas confundem:

snippetJavaScript
await fetch('/api/users');

com paralelismo. Mas await apenas libera a thread enquanto espera uma resposta de rede. Tudo continua acontecendo na mesma thread — o event loop cuida de outras tarefas enquanto a rede responde.

Web Workers são diferentes:

snippettext
Async/await (fetch)          Web Workers─────────────────────         ─────────────────────Uma thread                    Main Thread + Worker ThreadEspera I/O                    Calcula em paraleloNão bloqueia rede             Não bloqueia interface
Async/AwaitWeb Workers
ThreadsUmaVárias
Ideal paraEsperar APIs, timers, redeCálculos CPU-intensivos
Bloqueia interface?Não (em I/O)Não (trabalho vai para Worker)
Paralelismo real?NãoSim (threads separadas)

Regra simples: Async é para esperar. Workers são para calcular.

06

seção

Criando um Worker

Arquivo principal (Main Thread)

snippetJavaScript
const worker = new Worker('./worker.js');

O arquivo worker.js será executado em outra thread. O caminho é relativo à página que criou o Worker.

Forma moderna: Module Workers

snippetJavaScript
// Permite import/export dentro do Workerconst worker = new Worker('./worker.js', {  type: 'module',});

Com type: "module", você pode usar import e export dentro do Worker — útil para compartilhar utilitários sem poluir o escopo global.

07

seção

Comunicação entre Threads

Como as threads possuem memórias separadas, elas não compartilham variáveis. A comunicação acontece exclusivamente via mensagens.

Main Thread → Worker

snippetJavaScript
// main.jsconst worker = new Worker('./worker.js');worker.postMessage('Olá Worker!');
snippetJavaScript
// worker.jsself.onmessage = (event) => {  console.log(event.data); // "Olá Worker!"};

Worker → Main Thread

snippetJavaScript
// worker.jsself.onmessage = (event) => {  const nome = event.data;  self.postMessage(`Olá ${nome}`);};
snippetJavaScript
// main.jsworker.onmessage = (event) => {  console.log(event.data); // "Olá Vander"}; worker.postMessage('Vander');

Structured Clone: postMessage copia os dados usando o algoritmo Structured Clone — objetos simples, arrays, ArrayBuffer, Map, Set funcionam. Funções e referências DOM não podem ser transferidas.

08

seção

Exemplo Prático: Soma Pesada

main.js:

snippetJavaScript
const worker = new Worker('./worker.js'); worker.onmessage = (event) => {  console.log('Resultado:', event.data);}; worker.postMessage(1_000_000_000);

worker.js:

snippetJavaScript
self.onmessage = (event) => {  let soma = 0;   for (let i = 0; i < event.data; i++) {    soma += i;  }   self.postMessage(soma);};

Enquanto o cálculo acontece na thread do Worker:

  • A interface continua fluida
  • Botões continuam funcionando
  • Animações continuam sendo renderizadas

Compare com executar o mesmo loop na Main Thread — a página congelaria por vários segundos.

09

seção

O Objeto Global do Worker

Na thread principal temos window. Dentro de um Worker usamos self:

snippetJavaScript
// worker.jsself.postMessage('Finalizado'); // self === this dentro de um Worker dedicadoconsole.log(self === this); // true

Workers dedicados expõem um subconjunto limitado de APIs do navegador — suficiente para cálculos, fetch (sim, Workers podem fazer fetch!), timers e importScripts, mas sem acesso ao DOM.

10

seção

Workers NÃO Possuem Acesso ao DOM

Este é um dos pontos mais importantes — e uma fonte comum de confusão.

Dentro de um Worker, isto gera erro:

snippetJavaScript
document.getElementById('titulo'); // ❌ ReferenceError

Também não existem:

snippetJavaScript
window      // ❌document    // ❌localStorage  // ❌alert()       // ❌

Isso acontece porque o DOM não é thread-safe — permitir acesso concorrente causaria condições de corrida imprevisíveis.

Então quem altera a interface?

O Worker apenas calcula. A Main Thread continua responsável pelo DOM:

snippetJavaScript
// worker.jsself.postMessage(resultado); // main.jsworker.onmessage = (event) => {  resultadoElement.textContent = event.data;};
11

seção

Fluxo Completo

snippettext
Usuário clica      ↓Main Thread      ↓postMessage(dados)      ↓Worker Thread      ↓Processamento pesado      ↓postMessage(resultado)      ↓Main Thread      ↓Atualiza DOM

Esse padrão — delegar → processar → retornar → renderizar — é a arquitetura padrão para qualquer feature que use Workers: processamento de imagem, parser de CSV, compressor de arquivos, etc.

12

seção

Encerrando um Worker

Workers continuam vivos enquanto existirem — consumindo memória e CPU. Quando não forem mais necessários, encerre explicitamente:

snippetJavaScript
worker.terminate();

Isso libera recursos do navegador imediatamente. O Worker também pode encerrar a si mesmo:

snippetJavaScript
// worker.jsself.close();

Boas práticas: Crie Workers sob demanda, reutilize quando fizer sentido (pool de Workers), e sempre terminate() quando a tarefa terminar ou o componente for desmontado.

13

seção

Analogia Final: Escritório

Imagine um escritório. O gerente atende clientes o tempo inteiro (Main Thread). Mas existe uma equipe interna responsável por tarefas demoradas (Workers).

snippettext
Clientes    ↓Gerente (Main Thread) — sempre disponível    ↓Equipe interna (Workers) — trabalho pesado    ↓Resultados enviados de volta    ↓Gerente apresenta ao cliente (atualiza DOM)

O gerente nunca para de atender porque a equipe trabalha em paralelo. É exatamente isso que acontece no navegador quando você usa Web Workers corretamente.

14

seção

Teste seus conhecimentos

$ summary --quick

Resumo rápido

  • JavaScript continua single-threaded na Main Thread — Workers criam threads adicionais
  • Workers são ideais para tarefas CPU-intensivas, não para I/O (fetch, timers)
  • Async é para esperar; Workers são para calcular — não confunda concorrência com paralelismo
  • Comunicação via postMessage() — memórias separadas, sem variáveis compartilhadas
  • Workers não acessam o DOM — a Main Thread atualiza a interface com os resultados
  • Use self como contexto global dentro do Worker
  • Encerre Workers com terminate() para liberar recursos
  • Module Workers (type: "module") permitem import/export no Worker

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.