Vue异步方法仍阻塞主线程?easystarjs路径计算卡顿DOM求解
问题分析与解决方案
你确实误解了Promise的作用——Promise不会将同步代码转换为异步执行。它只是提供了一种更优雅的方式处理异步回调,或者将回调式的异步任务包装成Promise风格,但如果Promise内部的代码是同步阻塞的(比如你的props.easystar.calculate()),那它依然会占用主线程,导致DOM冻结。
你的代码里,calcPath函数虽然返回了Promise,但easystar.calculate()是同步执行的路径计算逻辑,这部分会在主线程跑2-3秒,完全阻塞UI渲染,所以Promise并没有起到异步非阻塞的作用。
两种解决思路
1. 使用Web Worker将计算移到后台线程
Web Worker运行在独立的后台线程,不会占用主线程,适合处理这种耗时的计算任务。
主线程代码修改:
<script setup> const findPath = () => { console.log("findPath-before"); // 创建Worker实例(需提前创建path-worker.js文件) const pathWorker = new Worker('./path-worker.js'); // 向Worker发送计算所需的参数 pathWorker.postMessage({ start: props.party.pos, destination: props.party.destinations[0], acceptableTiles: [1, 0], plainTileCost: 4, grid: /* 这里传入你的地图网格数据 */ }); // 接收Worker返回的计算结果 pathWorker.onmessage = (event) => { const path = event.data; if (path === null) { alert("Path was not found."); } else { props.party.path = { current: 0, data: path }; console.log(true); } // 计算完成后销毁Worker pathWorker.terminate(); }; console.log("findPath-after"); }; </script> <template> <Button @click="findPath"> <span>Load path</span> </Button> </template>
Worker文件(path-worker.js):
// 引入easystar库(注意路径要正确) importScripts('lib/easystar.min.js'); self.onmessage = (event) => { const { start, destination, acceptableTiles, plainTileCost, grid } = event.data; const easystar = new EasyStar.js(); easystar.setGrid(grid); easystar.setAcceptableTiles(acceptableTiles); easystar.setTileCost(plainTileCost, 4); easystar.findPath( start.x, start.y, destination.x, destination.y, (path) => { // 将计算结果发送回主线程 self.postMessage(path); } ); easystar.calculate(); };
2. 使用easystar的分帧计算功能
easystar提供了setIterationsPerCalculation方法,可以将路径计算拆分成多个小批次执行,每次只运行指定次数的迭代,这样不会一次性阻塞主线程,给DOM渲染留出生存空间。
修改你的calcPath函数:
<script setup> const findPath = async () => { console.log("findPath-before"); const result = await calcPath(); console.log(result); console.log("findPath-after"); }; const calcPath = () => { return new Promise((resolve) => { if ( props.party.path || (props.party.pos.x === props.party.destinations[0].x && props.party.pos.y === props.party.destinations[0].y) ) { resolve(false); return; } props.easystar.setAcceptableTiles([1, 0]); props.easystar.setTileCost(Terrain.Plain, 4); // 设置每次计算的迭代次数,数值越小越不阻塞UI,可根据地图大小调整 props.easystar.setIterationsPerCalculation(100); props.easystar.findPath( props.party.pos.x, props.party.pos.y, props.party.destinations[0].x, props.party.destinations[0].y, (path) => { if (path === null) { alert("Path was not found."); resolve(false); } else { props.party.path = { current: 0, data: path }; resolve(true); } } ); // 异步分帧计算,不会阻塞主线程 props.easystar.calculate(); }); }; </script>
内容的提问来源于stack exchange,提问作者Nico Shultz
相关产品推荐
相关产品推荐

