能,但必须用显式 promise 缓存 + 请求去重队列实现;sharedworker 不共享 localstorage 或全局变量,需以 requestkey 为键的 map 管理 active promise,并配合 abortcontroller、超时控制、port.close() 清理及业务字段(如 force)控制缓存策略。

能,但必须绕过 SharedWorker 默认无状态、无缓存的陷阱,用显式 Promise 缓存 + 请求去重队列实现。直接在 SharedWorker 里 localStorage.setItem() 或存全局对象是无效的——它不共享内存,也不持久化。
SharedWorker 里不能直接用 localStorage 或全局变量做缓存
SharedWorker 确实运行在独立上下文,但它和页面一样,localStorage 是按源隔离的,每个页面连的还是自己的 storage;而全局变量(比如 cache = new Map())看似可行,实际会因 Worker 被浏览器回收(如所有页面关闭后又重开)而丢失,且无法跨重启保留。
更关键的是:多个页面并发请求同一 URL 时,若没加锁,Worker 会并行发起多次 fetch,完全起不到去重作用。
- 不要写
cache.set(url, response)后就直接 resolve —— 下一个请求进来时 cache 还没写完,照样会再发一次 - 不要在
onconnect外层声明const pending = new Map()—— 它不会跨 Worker 实例存活,且无法被新连接的 port 访问到一致状态 - 浏览器可能随时终止空闲 SharedWorker,所以不能依赖其内存长期缓存;需配合 IndexedDB 做落地,但首次冷启仍要防并发
用 Promise 池 + Map 键控实现请求级去重
核心思路是:对相同请求参数(如 { url: '/api/user', method: 'GET' }),只允许一次 fetch 执行,后续请求都 await 同一个 Promise 实例。这需要把 Promise 和 resolve/reject 存进一个共享 Map,并确保 key 足够唯一(推荐 JSON.stringify 规范化参数)。
const activeRequests = new Map(); // key: requestKey, value: { promise, resolve, reject }
function getRequestKey(req) {
return JSON.stringify({
url: req.url,
method: req.method || 'GET',
body: req.body ? JSON.stringify(req.body) : undefined
});
}
self.onconnect = (e) => {
const port = e.ports[0];
port.start();
port.onmessage = (e) => {
const { id, type, request } = e.data;
if (type === 'fetch') {
const key = getRequestKey(request);
let entry = activeRequests.get(key);
if (!entry) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
entry = {
promise: fetch(request.url, { ...request, signal: controller.signal })
.then(r => r.json())
.catch(err => { throw err.name === 'AbortError' ? new Error('Request timeout') : err; })
.finally(() => {
clearTimeout(timeout);
activeRequests.delete(key);
}),
resolve: null,
reject: null
};
activeRequests.set(key, entry);
}
// 所有同 key 请求都复用这个 promise
entry.promise
.then(data => port.postMessage({ id, result: data }))
.catch(err => port.postMessage({ id, error: err.message }));
}
};
};
- key 必须包含 method 和规范化 body,否则 POST /user 和 GET /user 会被当成同一请求
- 务必用
AbortController配合超时,防止某个请求卡死拖垮整个队列 -
finally中清理activeRequests,否则内存泄漏;但注意不要在 catch 后提前删,得等 promise 真正 settle
页面端调用时必须传唯一 id 并正确处理 port.close()
SharedWorker 不自动感知页面关闭,如果页面刷新或崩溃,它持有的 Promise 会一直 pending,后续请求被 block。必须由页面主动通知“我走了”。
页面侧代码关键点:
const worker = new SharedWorker('/sw-cache.js');
const port = worker.port;
// 发送带 id 的请求
let reqId = 0;
function fetchShared(url, options = {}) {
const id = ++reqId;
port.postMessage({ id, type: 'fetch', request: { url, ...options } });
return new Promise((resolve, reject) => {
const onMessage = (e) => {
if (e.data.id === id) {
if (e.data.result) resolve(e.data.result);
else reject(new Error(e.data.error));
port.removeEventListener('message', onMessage);
}
};
port.addEventListener('message', onMessage);
});
}
// 页面卸载前关闭 port,触发 Worker 的 onclose
window.addEventListener('beforeunload', () => {
port.close();
});
- 每次调用
fetchShared()必须生成新id,否则多个请求会互相覆盖响应 -
port.close()必须在beforeunload里调,不能只靠visibilitychange—— 用户直接关标签页时后者不触发 - Worker 端需监听
port.onclose并手动清理对应 pending 请求(如果有的话),但 Promise 池本身靠finally清理更可靠
缓存失效与强制刷新需要额外协议字段
纯去重不够,业务常需要“跳过缓存”或“强制更新”。不能靠时间戳拼 URL(那就不去重了),应在请求体里加控制字段:
- 加
force: true字段 → Worker 不查 Promise 池,直接发起新 fetch 并覆盖旧 entry - 加
staleWhileRevalidate: true→ 先返回缓存(如果有),再后台拉新数据并广播更新 - 服务端响应带
Cache-Control: no-store时,Worker 应跳过缓存逻辑,避免污染
真正难的不是写代码,而是想清楚:哪些请求该去重、哪些该强制走网络、哪些该降级返回 stale 数据——这些策略没法交给 SharedWorker 自动判断,得由业务层通过字段明确表达。否则,你写的“缓存层”可能变成“阻塞层”。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











