Web Worker中Go-WebAssembly加载前触发onmessage的问题求助
解决Web Worker中Go-WebAssembly加载前触发onmessage的问题
核心问题
主进程初始化Worker后立刻发送消息,但此时WebAssembly还在加载过程中,myGoFunction尚未被Go代码挂载到Worker的全局上下文,导致调用时出现"myGoFunction is not defined"错误。
可行解决方案
方案1:缓存消息队列 + 加载完成后批量处理
在Worker中先将收到的消息缓存到队列,等WASM加载并执行完成后,再逐个处理队列中的消息,既保证消息不丢失,也能避免提前调用未就绪的WASM函数。
修改后的worker.js代码:
importScripts('wasm_exec.js'); const go = new Go(); // 初始化消息缓存队列 const messageQueue = []; let wasmReady = false; // 先注册临时的onmessage,缓存未就绪时收到的消息 onmessage = (e) => { if (!wasmReady) { messageQueue.push(e); return; } // WASM已就绪,直接处理消息 handleMessage(e); }; // 加载并运行WASM,完成后标记就绪并处理缓存消息 WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject) .then((result) => { go.run(result.instance); wasmReady = true; // 批量处理缓存的消息 messageQueue.forEach(msg => handleMessage(msg)); messageQueue.length = 0; }); // 抽离消息处理逻辑到单独函数 const handleMessage = (e) => { const {settings} = e.data; if (settings) { // 设置参数的逻辑 } else { const someArray = []; for (let i= 0; i < 1000000; i++) { someArray[i] = calculate(i); } postMessage({someArray}); } }; const calculate = (i) => { // 其他业务逻辑 return myGoFunction(i); };
方案2:主进程等待WASM就绪信号
让Worker在WASM加载完成后主动向主进程发送"就绪"信号,主进程收到信号后再开始发送任务消息,从根源上避免过早触发消息处理。
修改后的worker.js:
importScripts('wasm_exec.js'); const go = new Go(); WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject) .then((result) => { go.run(result.instance); // 向主进程发送WASM就绪信号 postMessage({type: 'wasm-ready'}); // 此时再注册正式的消息处理函数 onmessage = handleMessage; }); const handleMessage = (e) => { const {settings} = e.data; if (settings) { // 设置参数的逻辑 } else { const someArray = []; for (let i= 0; i < 1000000; i++) { someArray[i] = calculate(i); } postMessage({someArray}); } }; const calculate = (i) => { // 其他业务逻辑 return myGoFunction(i); };
修改后的main.js:
let worker; const myArray = [/* 你的任务数据集合 */]; const doSomething = () => { if (myArray.length > 0) { worker.postMessage({ value: myArray.shift() }); } }; const init = () => { if (worker) worker.terminate(); worker = new Worker('worker.js'); worker.onmessage = (e) => { if (e.data.type === 'wasm-ready') { // WASM就绪,先发送初始化设置 worker.postMessage({ settings: { a: A, b: B, bool: true } }); // 切换到任务处理的消息回调 worker.onmessage = (res) => { // 处理Worker返回的任务结果 // ... // 继续下一个任务 doSomething(); }; // 启动第一个任务 doSomething(); } }; }; init();
方案3:用Promise封装WASM加载(避免重复加载)
将WASM加载逻辑封装为一个全局Promise,确保calculate函数调用时WASM已经就绪,同时保证WASM只会被加载一次(解决你之前重复加载导致速度慢的问题)。
修改后的worker.js:
importScripts('wasm_exec.js'); const go = new Go(); // 封装WASM加载为Promise,仅执行一次 const wasmLoadPromise = WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject) .then((result) => { go.run(result.instance); return true; }); onmessage = async (e) => { // 等待WASM加载完成,后续调用直接复用已完成的Promise状态 await wasmLoadPromise; const {settings} = e.data; if (settings) { // 设置参数的逻辑 } else { const someArray = []; for (let i= 0; i < 1000000; i++) { someArray[i] = calculate(i); } postMessage({someArray}); } }; const calculate = (i) => { // 其他业务逻辑 return myGoFunction(i); };
总结
- 方案1适合需要立即接收初始化消息但不急于处理的场景,通过缓存队列保证消息不丢失。
- 方案2逻辑清晰,主进程与Worker通过信号同步,完全避免过早触发任务处理。
- 方案3代码最简洁,利用Promise特性天然保证WASM只加载一次,适合大多数业务场景。
内容的提问来源于stack exchange,提问作者insurg3nt3
相关产品推荐
相关产品推荐

