Async/Await在Event Emitter中未按预期工作,请求MongoDB数据时不等待
网格寻宝游戏异步回调问题
我正在开发一款简单的网格寻宝游戏练手,前端采用HTML+JS实现,后端基于Node.js与Mongoose构建。游戏逻辑是玩家点击网格完成移动,当抵达宝藏tile时,后端需要从MongoDB获取winCode并返回给前端。
miApp.allGames是存储当前所有游戏实例的currentUserInfoMov对象数组,每个浏览器通过本地存储对应一个实例,这部分功能运行正常。
问题现象
movementsEmitter不会等待MongoDB的异步查询操作完成,控制台日志输出顺序如下:
- you win!
- Sent to front
- Emitter finished
相关代码
后端接口代码
routerApi.post("/clicked", async (req, res) => { try { miApp.movementsEmitter.emit("playerWantToMove", req.body, grid); const currentUserInfoMov = miApp.allGames.filter(x => x.Id == req.body.id)[0]; res.send(currentUserInfoMov); console.log("Sent to Front"); } catch (error) { console.log(error) } })
movementsEmitter监听代码
movementsEmitter.on("playerWantToMove", async (tileClickedAndId, grid) => { ...其他功能正常的代码... if (playerDestiny == treasureTile) { console.log("you win!"); const bottle = await Bottles.findById(BOTTLEID); currentUserInfoMov.winCode = bottle.codeToWin; TakeBottle(); console.log("Emitter finished"); } }
问题原因
emit触发的异步监听函数不会阻塞后续代码执行。接口中在调用emit后,立刻就从miApp.allGames中获取实例并返回响应,此时MongoDB的findById异步查询还未完成,winCode还没被赋值到currentUserInfoMov对象中。
解决方案
方案1:用Promise包装事件触发,等待异步处理完成
修改后端接口代码,将emit操作包装为Promise,确保等待事件监听中的异步逻辑执行完毕后再返回响应:
routerApi.post("/clicked", async (req, res) => { try { // 包装事件触发为Promise,等待异步逻辑完成 await new Promise((resolve) => { miApp.movementsEmitter.emit("playerWantToMove", req.body, grid, resolve); }); // 用find替代filter提升效率 const currentUserInfoMov = miApp.allGames.find(x => x.Id == req.body.id); res.send(currentUserInfoMov); console.log("Sent to Front"); } catch (error) { console.error(error); res.status(500).send("服务器错误"); } })
同时修改movementsEmitter的监听代码,接收resolve参数并在异步逻辑完成后调用:
movementsEmitter.on("playerWantToMove", async (tileClickedAndId, grid, resolve) => { ...其他功能正常的代码... if (playerDestiny == treasureTile) { console.log("you win!"); const bottle = await Bottles.findById(BOTTLEID); currentUserInfoMov.winCode = bottle.codeToWin; TakeBottle(); console.log("Emitter finished"); } // 通知Promise异步处理完成 resolve(); })
方案2:将响应逻辑移至事件监听中(耦合性较高)
如果不想修改事件传递方式,可以把返回响应的逻辑放到监听函数里,注意要传递req和res对象:
routerApi.post("/clicked", async (req, res) => { try { miApp.movementsEmitter.emit("playerWantToMove", req.body, grid, req, res); } catch (error) { console.error(error); res.status(500).send("服务器错误"); } })
监听代码修改:
movementsEmitter.on("playerWantToMove", async (tileClickedAndId, grid, req, res) => { ...其他功能正常的代码... let currentUserInfoMov = miApp.allGames.find(x => x.Id == tileClickedAndId.id); if (playerDestiny == treasureTile) { console.log("you win!"); const bottle = await Bottles.findById(BOTTLEID); currentUserInfoMov.winCode = bottle.codeToWin; TakeBottle(); console.log("Emitter finished"); } res.send(currentUserInfoMov); console.log("Sent to Front"); })
内容的提问来源于stack exchange,提问作者dandomin
相关产品推荐
相关产品推荐

