shared worker 无法在页面崩溃时自动保持连接,仅当至少一个同源页面存活时才维持运行;其 websocket 连接需在 worker 内统一管理并实现指数退避重连、心跳保活与错误隔离,页面通过 postmessage 协同通信。

Shared Worker 崩溃时连接不会自动保持,它本身不解决页面崩溃问题
Shared Worker 无法“在页面崩溃时保持连接”——页面进程一旦崩溃(如 JS 主线程卡死、OOM、强制 kill),整个渲染进程终止,其中运行的 Shared Worker 也会被系统回收。浏览器不提供跨进程崩溃恢复机制。所谓“保持长连接”,实际依赖的是 Shared Worker 在多个页面间共享状态的能力,而非容灾能力。
真正能做的,是把连接逻辑下沉到 Shared Worker 中,并让存活页面接管重连责任。关键点在于:连接生命周期脱离单个页面,由 Shared Worker 统一管理;页面崩溃后,只要还有其他标签页存活,Shared Worker 就持续运行,连接可维持;若全部页面关闭,Shared Worker 自动终止,此时需靠新页面启动时触发重连。
-
SharedWorker实例在所有同源页面间共享,但仅当至少一个页面调用new SharedWorker()且未关闭其port时才存活 - 页面崩溃或刷新会关闭其持有的
port,但不立即销毁 Worker —— 只有最后一个port关闭后,Worker 才在约 10–30 秒后被浏览器回收(具体时间不可控) - 连接(如
WebSocket)必须在 Shared Worker 内创建和维护,不能由页面直接 new;否则页面一崩,socket 就断
如何在 Shared Worker 中安全创建并维持 WebSocket 连接
Shared Worker 环境不支持 fetch 或 XMLHttpRequest,但支持 WebSocket。注意:Worker 内没有 window、document,也没有 localStorage,只能用 self 和 postMessage 通信。
核心是封装重连逻辑 + 心跳保活 + 错误隔离。不要依赖 onclose 判断是否该重连——它可能在连接未真正建立时就触发(如 DNS 失败),应结合 onerror 和超时控制。
- 使用
setTimeout实现指数退避重连(如 1s → 2s → 4s → 最大 30s),避免雪崩请求 - 每次连接前检查
ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CONNECTING,防止重复new WebSocket() - 心跳用
setInterval发送ping消息,服务端需响应pong;连续 2 次无响应则主动close()并触发重连 - 所有
ws.onopen/onmessage/onerror回调内必须加try...catch,否则未捕获异常会导致 Worker 崩溃(整个 Shared Worker 实例退出)
// shared-worker.js
let ws = null;
let reconnectTimer = null;
let heartbeatTimer = null;
<p>function connect() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;</p><p>ws = new WebSocket('wss://api.example.com/realtime');</p><p>ws.onopen = () => {
clearInterval(heartbeatTimer);
heartbeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'ping' }));
}, 25000);
};</p><p>ws.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
// 向所有页面广播
self.clients.matchAll().then(clients => {
clients.forEach(client => client.postMessage(data));
});
} catch (err) {
// 静默丢弃非法消息,不 throw
}
};</p><p>ws.onerror = (err) => {
console.error('[SW] WS error:', err);
cleanup();
scheduleReconnect();
};</p><p>ws.onclose = () => {
console.log('[SW] WS closed');
cleanup();
scheduleReconnect();
};
}</p><p>function cleanup() {
if (heartbeatTimer) clearInterval(heartbeatTimer);
if (ws) ws.close();
ws = null;
}</p><p>function scheduleReconnect() {
if (reconnectTimer) clearTimeout(reconnectTimer);
const delay = Math.min(30000, (reconnectTimer ? delay * 2 : 1000));
reconnectTimer = setTimeout(connect, delay);
}</p>
页面如何与 Shared Worker 协同实现“崩溃后自动恢复”
页面自身崩溃无法感知,但可通过定期心跳检测 Shared Worker 是否存活。更可靠的做法是:每个页面加载时主动注册自己,并监听 Worker 的连接状态广播;当页面意外关闭(如刷新、崩溃),Shared Worker 能通过 client.ports 变化感知,而新页面启动后立即询问当前连接状态,决定是否触发重连。
- 页面首次连接 Shared Worker 后,立刻发
{ type: 'register' }消息,Worker 记录活跃 client 列表 - 页面监听
beforeunload发送{ type: 'unregister' }(虽崩溃时不一定执行,但正常场景有用) - Shared Worker 收到新
port时,先广播当前ws.readyState;若为CLOSED或CONNECTING,新页面可选择等待或主动触发重连请求(如发{ type: 'force-reconnect' }) - 页面中不要直接操作
ws,所有通信走port.postMessage();Shared Worker 收到消息后统一转发到 socket 或内部处理
注意:self.clients.matchAll() 在部分浏览器(如 Safari)中受限,需降级处理:Worker 可维护一个 client ID 映射表,每次 port.start() 时生成 ID 并存入,port.close 时清理。
Chrome/Firefox/Safari 兼容性与真实限制
Shared Worker 在 Chrome 和 Firefox 中表现稳定,但在 Safari 中存在明显缺陷:从 iOS 15.4 开始支持,但 self.clients.matchAll() 返回空数组,且 port 关闭后 Worker 可能延迟数分钟才终止。这意味着你无法准确判断是否只剩一个页面,也无法可靠广播消息给“所有页面”。
- Safari 不支持
self.clients,必须改用MessageChannel+ 页面侧主动上报状态 - 所有浏览器都不允许 Shared Worker 访问
navigator.geolocation、Notification等 API,涉及权限的操作必须交还页面 - 如果服务端连接要求 cookie 或
withCredentials: true,Shared Worker 中的WebSocket会自动携带同源 cookie,但首次连接可能因 CORS 预检失败(WebSocket 不走预检,但某些网关会拦截) - 内存泄漏风险高:忘记
port.close()、未清除setInterval、未解绑onmessage监听器,都会导致 Shared Worker 持续占用资源
真正容易被忽略的是:Shared Worker 不是“后台服务”,它没有持久化存储、没有唤醒机制、不支持定时任务(setInterval 在页面全部关闭后会被暂停)。所谓“自动重连”,只是在有页面存活的前提下,把重连逻辑从易崩的页面 JS 移到了相对稳定的 Worker 线程里——本质是降低失败概率,不是消除失败。










