JavaScript战舰游戏:等待船只放置点击完成再执行下一个函数
解决连续调用船只放置函数的异步执行问题
问题核心
你当前的playerPlaceShip是同步函数,连续调用时会立刻绑定多轮事件监听,导致第二个函数在首次点击完成前就启动,同时叠加的事件监听会引发逻辑混乱。要实现按顺序等待每艘船放置完成,核心是将异步的用户交互(点击)转化为可等待的异步操作,同时保持游戏流程控制权在游戏循环模块。
解决方案
将playerPlaceShip改造为返回Promise的函数,在游戏循环中用async/await按顺序调用,确保每一轮放置完成后再启动下一轮。
1. 修改DOM模块:让playerPlaceShip返回Promise
// DOM模块中修改playerPlaceShip函数 const playerPlaceShip = (board, ship) => { return new Promise((resolve) => { const squares = document.querySelectorAll('#p1 .board .square'); narrative.textContent = `Lead your ${ship.type} into battle. Press X to steer.`; // 定义各事件处理函数 const handleClick = (e) => { clickToPlace(e, board, ship); resolve(); // 用户点击完成后,标记当前Promise完成 removeAllListeners(); // 清理当前轮次的所有事件监听 }; const handleMouseOver = (e) => renderShadow(e, 'fill', ship.length); const handleMouseOut = (e) => renderShadow(e, 'clear', ship.length); const handleKeyDown = (e) => { if(e.key === 'x') { switchAxis(); squares.forEach(square => square.classList.remove('hovered')); renderShadow(lastCoords, 'fill', ship.length); } }; // 绑定事件 squares.forEach(square => { square.addEventListener('mouseover', handleMouseOver); square.addEventListener('mouseout', handleMouseOut); square.addEventListener('click', handleClick); }); window.addEventListener('keydown', handleKeyDown); // 封装监听清理逻辑 function removeAllListeners() { squares.forEach(square => { square.removeEventListener('mouseover', handleMouseOver); square.removeEventListener('mouseout', handleMouseOut); square.removeEventListener('click', handleClick); }); window.removeEventListener('keydown', handleKeyDown); } }); }; // 原clickToPlace函数保持不变 const clickToPlace = (e, board, ship) => { let { x, y } = e.target.dataset; x = parseInt(x, 10); y = parseInt(y, 10); board.place(ship, x, y, axis); renderShadow(e, 'place', ship.length); };
2. 修改游戏循环模块:用async/await顺序调用
// 将放置流程封装为async函数 async function startPlayerShipPlacement() { // 渲染棋盘 renderBoard(p1Board, p1Box); renderBoard(p2Board, p2Box); // 依次等待每艘船放置完成 await playerPlaceShip(p1Board, p1Carrier); await playerPlaceShip(p1Board, p1Battleship); // 后续船只可继续添加await调用 } // 启动放置流程 startPlayerShipPlacement();
方案优势
- 流程可控:游戏循环模块通过
async/await完全控制放置顺序,不会将控制权转移到DOM模块。 - 无监听叠加:每轮放置完成后自动清理事件监听,避免多轮函数调用导致的事件冲突。
- 异步逻辑清晰:用Promise将用户点击的异步操作标准化,符合现代JS异步编程范式。
内容的提问来源于stack exchange,提问作者Pete
相关产品推荐
相关产品推荐

