现有同步函数调用能否替换为基于async/await的PostMessage跨帧调用方案?
首先明确:在完全不修改原有调用代码的前提下,该需求无法实现。
原因说明
浏览器JavaScript基于单线程事件循环模型运行,postMessage属于异步API,发送的消息对应的回调必须等当前同步执行栈完全清空后才会触发。你如果要在同步函数里阻塞等待异步消息返回,只会把整个JS线程卡死,不可能在不阻塞线程的前提下让同步函数等待异步结果再返回。
你之前尝试的async方案不生效的核心原因是:async函数的返回值永远是Promise对象,原有代码if(thisfunc() == 'something')是拿Promise实例和字符串做相等判断,结果永远为false,自然不符合预期。
方案1:最小改动兼容方案(推荐)
仅需要给所有调用thisfunc的上下文加async/await语法,IDE全局替换即可快速完成改造,成本极低,符合前端规范。
第一步:改造thisfunc实现
// 全局存储pending的请求,避免多并发冲突 const pendingRequests = new Map(); let reqSeq = 0; // 父帧域名白名单,必填,避免安全漏洞 const PARENT_ORIGIN = 'https://your-parent-domain.com'; // 全局监听父帧回传消息 window.addEventListener('message', (e) => { if (e.origin !== PARENT_ORIGIN) return; const { reqId, payload } = e.data; if (pendingRequests.has(reqId)) { pendingRequests.get(reqId)(payload); pendingRequests.delete(reqId); } }); async function thisfunc() { const currentReqId = reqSeq++; return new Promise((resolve) => { pendingRequests.set(currentReqId, resolve); // 给父帧发消息,请求ID需要父帧原样带回 window.parent.postMessage({ type: 'checkSomething', reqId: currentReqId }, PARENT_ORIGIN); }); }
第二步:改造原有调用逻辑
所有调用thisfunc的位置,只需要把当前执行上下文包裹为async函数,调用前加await即可:
// 改造前 /* function thisfunc(){ return checksomethingincurrentpage() } if(thisfunc() == 'something') // do something // do something else */ // 改造后 (async () => { if(await thisfunc() === 'something') { // 原有逻辑完全不需要修改 } // 原有其他逻辑完全不需要修改 })();
如果thisfunc是在其他自定义函数中调用,只需要给对应自定义函数加async前缀,调用该函数的位置加await即可,几百处调用用IDE全局匹配替换可以在几分钟内完成全部改造。
方案2:零调用代码改动Hack方案(极不推荐)
仅适用于内部工具类场景,生产环境禁止使用:
可以通过SharedArrayBuffer+Atomics原子操作实现同步阻塞等待,但存在大量限制:
- 要求页面和父帧都配置跨域隔离HTTP头:
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp - 阻塞等待期间整个页面JS线程完全停滞,页面无响应,用户体验极差
- 老旧浏览器不支持该API
不存在和同步XMLHttpRequest类似的postMessage同步调用模式,所有异步API都不可能在不阻塞线程的前提下在同步函数中等待返回结果,方案1是目前投入产出比最高的实现方式。
内容的提问来源于stack exchange,提问作者Dan Roberts

