如何在Service Worker中将onmessage获取的变量移出局部作用域
问题分析与解决
问题背景
你通过Broadcast Channel API向Service Worker(firebase-messaging-sw.js)发送数据,接收代码如下:
const mainChannel = new BroadcastChannel("main-channel"); mainChannel.onmessage = (data) => {console.log(data)}
尝试用全局变量translateData存储收到的数据,再在push事件处理函数中使用,但未成功,代码如下:
let translateData = {} const mainChannel = new BroadcastChannel("main-channel"); mainChannel.onmessage = (data) => { translateData = data; } self.addEventListener('push', (event) => { messaging.onBackgroundMessage(function (payload) { console.log(translateData) storeMessage(payload.data); const notificationTitle = payload.data.title; const notificationOptions = { body: payload.data.body }; return event.waitUntil(self.registration.showNotification(notificationTitle, notificationOptions)); }); });
为什么全局变量不行?
Service Worker的全局变量不可靠,原因有两个:
- 生命周期限制:Service Worker在闲置时会被浏览器终止,重启后全局变量会重置为初始值(比如你的
translateData会变回空对象)。 - 时序问题:
push事件可能在Broadcast消息收到之前就触发,此时translateData还没被赋值,自然拿不到数据。
另外要注意:BroadcastChannel的onmessage回调参数是MessageEvent对象,实际数据在event.data属性里,你之前直接把整个事件对象赋值给translateData,可能也不是你想要的结果。
解决方案
方案1:用IndexedDB持久化存储数据
这是最可靠的方案,数据会被持久化保存,即使Service Worker重启也能读取到。
第一步:封装IndexedDB操作函数
可以用原生IndexedDB代码实现,无需第三方库:
// 打开或创建IndexedDB数据库 async function openDB(name, version, upgradeCallback) { return new Promise((resolve, reject) => { const request = indexedDB.open(name, version); request.onupgradeneeded = (e) => upgradeCallback(request.result); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } // 存储翻译数据 async function saveTranslateData(data) { const db = await openDB('sw-translate-store', 1, (db) => { db.createObjectStore('translate'); }); const tx = db.transaction('translate', 'readwrite'); await tx.objectStore('translate').put(data, 'latest'); await tx.complete; db.close(); } // 读取最新翻译数据 async function getTranslateData() { const db = await openDB('sw-translate-store', 1); const tx = db.transaction('translate', 'readonly'); const data = await tx.objectStore('translate').get('latest'); await tx.complete; db.close(); return data || {}; }
第二步:修改Broadcast接收逻辑
收到消息时将数据存入IndexedDB:
const mainChannel = new BroadcastChannel("main-channel"); mainChannel.onmessage = async (event) => { // 存储实际数据event.data,而非整个事件对象 await saveTranslateData(event.data); };
第三步:在push事件中读取数据
self.addEventListener('push', (event) => { event.waitUntil( messaging.onBackgroundMessage(async (payload) => { const translateData = await getTranslateData(); console.log(translateData); storeMessage(payload.data); const notificationTitle = payload.data.title; const notificationOptions = { body: payload.data.body }; return self.registration.showNotification(notificationTitle, notificationOptions); }) ); });
方案2:用Promise缓存临时数据(非持久化)
如果只需要在Service Worker当前生命周期内共享数据,不需要持久化,可以用Promise缓存最新消息:
// 初始化一个resolve空对象的Promise let latestTranslateData = Promise.resolve({}); const mainChannel = new BroadcastChannel("main-channel"); mainChannel.onmessage = (event) => { // 更新Promise,保存最新数据 latestTranslateData = Promise.resolve(event.data); }; self.addEventListener('push', (event) => { event.waitUntil( messaging.onBackgroundMessage(async (payload) => { const translateData = await latestTranslateData; console.log(translateData); storeMessage(payload.data); const notificationTitle = payload.data.title; const notificationOptions = { body: payload.data.body }; return self.registration.showNotification(notificationTitle, notificationOptions); }) ); });
这个方案的缺点是:如果Service Worker被终止重启,缓存的数据会丢失,适合临时共享的场景。
关键注意点
- 所有异步操作必须放在
event.waitUntil()中,确保Service Worker在操作完成前不会被浏览器终止。 - 注意BroadcastChannel的
onmessage回调参数是MessageEvent,要取event.data才是你发送的实际数据。
内容的提问来源于stack exchange,提问作者Nika
相关产品推荐
相关产品推荐

