SharedWorker e BroadcastChannel
TL;DR
SharedWorkeré um worker compartilhado entre múltiplas abas e iframes da mesma origem — ideal para centralizar estado ou WebSockets (um só socket para todas as abas).BroadcastChannelé mais simples: permite broadcast de mensagens entre contextos da mesma origem sem um intermediário. Para sincronização simples entre abas, BroadcastChannel; para lógica centralizada, SharedWorker.
SharedWorker
Enquanto um Web Worker comum é exclusivo de uma aba, um SharedWorker é compartilhado entre todas as abas da mesma origem que o criarem com o mesmo URL.
// main.js (em cada aba)
const worker = new SharedWorker('/shared-worker.js');
// SharedWorker usa port para comunicação (diferente do postMessage direto)
worker.port.start();
// Enviar mensagem
worker.port.postMessage({ type: 'GREET', name: 'Alice' });
// Receber mensagem
worker.port.onmessage = (event) => {
console.log('Resposta do shared worker:', event.data);
};
// Alternativa: addEventListener
worker.port.addEventListener('message', (event) => {
console.log(event.data);
});
worker.port.start(); // obrigatório quando usar addEventListener// shared-worker.js
const ports = new Set(); // manter referência a todos os ports conectados
self.onconnect = (event) => {
const port = event.ports[0];
ports.add(port);
port.start();
port.onmessage = (event) => {
const { type, ...data } = event.data;
if (type === 'GREET') {
// Responder só para este port
port.postMessage({ type: 'GREETING', message: `Olá, ${data.name}!` });
}
if (type === 'BROADCAST') {
// Repassar para TODOS os ports (todas as abas)
broadcast({ type: 'NOTIFICATION', ...data }, port);
}
};
// Detectar aba fechada (não confiável, mas melhor que nada)
port.addEventListener('messageerror', () => {
ports.delete(port);
});
};
function broadcast(message, exceptPort = null) {
ports.forEach(p => {
if (p !== exceptPort) {
p.postMessage(message);
}
});
}Caso de uso: WebSocket compartilhado
Uma conexão WebSocket por aba é ineficiente. SharedWorker permite uma única conexão para todas:
// websocket-worker.js
let socket = null;
const ports = new Set();
self.onconnect = (event) => {
const port = event.ports[0];
ports.add(port);
port.start();
// Criar socket apenas na primeira conexão
if (!socket) {
socket = new WebSocket('wss://api.exemplo.com/ws');
socket.onmessage = (event) => {
// Repassar mensagem para todas as abas
ports.forEach(p => p.postMessage(JSON.parse(event.data)));
};
socket.onclose = () => {
socket = null;
// Opcional: reconectar com backoff
};
}
port.onmessage = (event) => {
if (socket?.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(event.data));
}
};
};
// main.js
const worker = new SharedWorker('/websocket-worker.js');
worker.port.start();
worker.port.postMessage({ type: 'SEND', payload: { action: 'subscribe', channel: 'news' } });
worker.port.onmessage = (event) => updateUI(event.data);BroadcastChannel
Mais simples que SharedWorker — broadcast direto entre contextos da mesma origem:
// Em qualquer aba/worker/iframe da mesma origem
const channel = new BroadcastChannel('app-channel');
// Enviar para TODAS as outras instâncias do mesmo canal
channel.postMessage({ type: 'USER_LOGGED_OUT', userId: '123' });
// Receber mensagens de outras instâncias
channel.onmessage = (event) => {
const { type, ...data } = event.data;
if (type === 'USER_LOGGED_OUT') {
clearUserState();
redirectToLogin();
}
};
// Alternativa com addEventListener
channel.addEventListener('message', handleMessage);
// Fechar (não recebe mais mensagens)
channel.close();BroadcastChannel vs evento
storageO evento
storagedo localStorage é parecido mas limitado: só dispara em outras abas, só para localStorage, só ao fechar/abrir, sem contexto de quem enviou.BroadcastChannelé mais explícito, suporta objetos (sem stringify), e funciona entre workers, iframes e abas.
Padrão: logout sincronizado entre abas
// auth.js — executado em cada aba
const authChannel = new BroadcastChannel('auth');
function logout() {
clearLocalState(); // limpar tokens em memória
sessionStorage.clear();
// Notificar outras abas
authChannel.postMessage({ type: 'LOGOUT', timestamp: Date.now() });
redirectToLogin();
}
// Reagir ao logout feito em outra aba
authChannel.onmessage = (event) => {
if (event.data.type === 'LOGOUT') {
clearLocalState();
redirectToLogin();
}
};Padrão: estado global sincronizado
// store-channel.js — sincronizar store Zustand/Redux entre abas
const storeChannel = new BroadcastChannel('store');
let isIncoming = false; // evitar loop
function setupCrossTabSync(store) {
// Enviar mudanças de estado para outras abas
store.subscribe((newState, prevState) => {
if (!isIncoming) {
storeChannel.postMessage({
type: 'STATE_CHANGE',
patch: diff(prevState, newState), // só enviar o que mudou
});
}
});
// Aplicar mudanças recebidas de outras abas
storeChannel.onmessage = (event) => {
if (event.data.type === 'STATE_CHANGE') {
isIncoming = true;
store.setState(applyPatch(store.getState(), event.data.patch));
isIncoming = false;
}
};
}Comparativo: SharedWorker vs BroadcastChannel
| SharedWorker | BroadcastChannel | |
|---|---|---|
| Complexidade | Alta (ports, connect event) | Baixa (postMessage direto) |
| Lógica centralizada | Sim (toda a lógica no worker) | Não (só mensageria) |
| Estado persistente entre recarregamentos | Sim (enquanto uma aba aberta) | Não |
| Disponibilidade | Boa (exceto Safari antes de 16) | Excelente |
| Comunicação bidirecional | Sim | Sim |
| Suporte a WebSocket centralizado | Sim | Não diretamente |
| Uso típico | Autenticação, WebSocket, cache | Sincronização de UI, logout, notificações |
Para fixar
- Por que SharedWorker usa
portem vez depostMessagediretamente?- Qual o evento que SharedWorker recebe quando uma nova aba se conecta?
- Qual o benefício de ter um único WebSocket em um SharedWorker vs. um por aba?
- O BroadcastChannel dispara na mesma aba que enviou a mensagem?
- Qual das duas APIs (SharedWorker vs BroadcastChannel) é melhor para sincronizar logout entre abas? Por quê?
Veja também
- 01 — Web Workers — anterior
- 03 — Service Workers e ciclo de vida — próxima
- Storage 01 — evento storage — alternativa simples ao BroadcastChannel