Chrome扩展Service Worker中如何等待变量赋值后执行操作?
我正在开发一款将语音转文字内容写入Google文档的Chrome扩展,在Service Worker中通过端口消息周期性接收处理后的文本并写入文档,同时处理用户的各类命令(如插入新段落、创建项目符号列表等)。当前使用checkCommand(currCommand)函数处理命令,代码如下:
const checkCommand = (cmd) => { const commands = { 'foo command': () => { console.log('This command should create a bullet list'); if(currListType !== null){ callGoogleAPIScript(myToken, 'createBulletList',currListType); formatingMode = false; } }, 'bar command': () => { console.log('This command should create a new paragraph'); callGoogleAPIScript(myToken, 'newParagraph'); formatingMode = false; }, }; if (cmd && commands.hasOwnProperty(cmd)) { commands[cmd](); // needed [cmd] because split words } else { console.log('Command dosen't exist'); formatingMode = false; }};
需求与问题
需要为命令设置特定属性(比如currListType,用于指定项目符号类型:圆形、数字等),必须等该变量完成赋值后,再执行callGoogleAPIScript(myToken, 'createBulletList',currListType)。
曾尝试用Promise+setTimeout实现等待,但Service Worker中setTimeout不可靠,且明明currListType已被全局正确设置,Promise却出现阻塞。
可行解决方案
方案一:消息驱动(推荐,适配Service Worker事件模型)
放弃轮询,将currListType的赋值与命令执行绑定为事件流,利用Service Worker的事件驱动特性实现同步:
// 维护待执行命令队列 let pendingCommands = []; let currListType = null; // 监听列表类型设置消息 chrome.runtime.onMessage.addListener((message) => { if (message.type === 'setListType') { currListType = message.listType; // 检查是否有等待该类型的命令 const pendingIndex = pendingCommands.findIndex(cmd => cmd.name === 'foo command'); if (pendingIndex !== -1) { // 执行命令 callGoogleAPIScript(myToken, 'createBulletList', currListType); formatingMode = false; // 移除队列中的待执行命令 pendingCommands.splice(pendingIndex, 1); } } }); // 修改checkCommand函数 const checkCommand = (cmd) => { const commands = { 'foo command': () => { console.log('准备创建项目符号列表'); if (currListType !== null) { callGoogleAPIScript(myToken, 'createBulletList', currListType); formatingMode = false; } else { // 将命令加入待执行队列 pendingCommands.push({ name: cmd }); // 设置超时兜底,避免无限等待 setTimeout(() => { const index = pendingCommands.findIndex(c => c.name === cmd); if (index !== -1) { pendingCommands.splice(index, 1); formatingMode = false; console.log('等待列表类型超时,取消命令'); } }, 5000); // 5秒超时阈值可按需调整 } }, 'bar command': () => { console.log('创建新段落'); callGoogleAPIScript(myToken, 'newParagraph'); formatingMode = false; }, }; if (cmd && commands.hasOwnProperty(cmd)) { commands[cmd](); } else { console.log('命令不存在'); formatingMode = false; } };
使用时,在设置currListType的地方(如Popup或Content Script)发送消息给Service Worker即可:
// 示例:在Popup中选择列表类型后发送消息 chrome.runtime.sendMessage({ type: 'setListType', listType: 'circle' });
方案二:基于自定义事件的等待(替代不可靠的setTimeout)
通过自定义事件监听变量变化,配合AbortController管理等待逻辑,避免轮询阻塞:
// 等待currListType赋值的函数 function waitUntilListTypeSet() { return new Promise((resolve, reject) => { const abortController = new AbortController(); const signal = abortController.signal; // 监听自定义事件 const handleListTypeSet = () => { if (currListType !== null) { abortController.abort(); resolve(); } }; self.addEventListener('listTypeSet', handleListTypeSet); // 设置超时兜底 const timeoutId = setTimeout(() => { self.removeEventListener('listTypeSet', handleListTypeSet); reject(new Error('等待列表类型超时')); }, 5000); // 清理资源 signal.addEventListener('abort', () => { clearTimeout(timeoutId); self.removeEventListener('listTypeSet', handleListTypeSet); }); }); } // 修改checkCommand中的foo command处理 const checkCommand = (cmd) => { const commands = { 'foo command': async () => { console.log('准备创建项目符号列表'); try { await waitUntilListTypeSet(); callGoogleAPIScript(myToken, 'createBulletList', currListType); formatingMode = false; } catch (err) { console.log(err.message); formatingMode = false; } }, 'bar command': () => { console.log('创建新段落'); callGoogleAPIScript(myToken, 'newParagraph'); formatingMode = false; }, }; if (cmd && commands.hasOwnProperty(cmd)) { commands[cmd](); } else { console.log('命令不存在'); formatingMode = false; } };
需要在设置currListType的地方触发自定义事件:
// 设置currListType后发送消息触发事件 chrome.runtime.sendMessage({ type: 'setListType', listType: 'number' }, () => { chrome.runtime.sendMessage({ event: 'listTypeSet' }); }); // 在Service Worker中监听消息并触发自定义事件 chrome.runtime.onMessage.addListener((message) => { if (message.event === 'listTypeSet') { self.dispatchEvent(new Event('listTypeSet')); } });
原方案阻塞原因
之前的轮询依赖setTimeout,但Service Worker可能因空闲进入休眠状态,导致定时器无法按时触发;此外轮询逻辑未绑定变量变化的事件,可能出现currListType已设置但轮询函数未及时检测到的情况。消息驱动或自定义事件的方式更贴合Service Worker的事件模型,避免了休眠带来的问题。
内容的提问来源于stack exchange,提问作者VladTbk321

