Node.js中如何处理map函数内的异步调用避免并行执行问题
问题根源
- 调用方式错误:
await allPlayers.map(...)中map是同步方法,返回的是未执行的Promise数组,await无法等待所有异步任务完成,导致所有updateGameScore直接并发触发 - 写入竞争冲突:你原有逻辑是「先查询是否存在,再决定新增/更新」,这个操作本身不是原子性的。并发场景下同一个userId的多个请求会同时查询到空结果,同时触发新增逻辑,最终产生重复记录
- 手动计算总分的逻辑不安全:并发场景下多个请求同时读取旧总分累加后写入,会出现覆盖问题,导致总分计算错误
解决方案
最优方案:使用MongoDB原子upsert操作(推荐)
直接用MongoDB自带的原子更新操作,从数据库层面避免竞争问题,性能远高于手动先查后写,并发场景下也不会出问题:
const updateGameScore = async (userId, gameId, score, date) => { await GameScore.findOneAndUpdate( // 查询条件:匹配对应用户ID { userId }, // 存在匹配记录时执行的原子操作 { $inc: { totalScore: score }, // 原子累加总分,避免并发覆盖 $set: { date }, // 如果需要更新对应游戏的最高分,可开启下行 // $max: { "gameScores.highScore": score } }, { upsert: true, // 不存在匹配记录时自动新建 setDefaultsOnInsert: true, // 新建记录时插入的默认字段 upsertData: { userId, gameScores: { gameId, highScore: score }, totalScore: score, date } } ) }
调用方式修正
如果使用了上述原子upsert方案,直接并发批量处理即可,5万条数据建议分批处理避免数据库压力过大:
const allPlayers = await Player.find(); const BATCH_SIZE = 1000; // 每批处理条数可根据服务器性能调整 // 分批执行 for (let i = 0; i < allPlayers.length; i += BATCH_SIZE) { const currentBatch = allPlayers.slice(i, i + BATCH_SIZE); // 等待当前批次所有任务执行完成 await Promise.all( currentBatch.map(player => updateGameScore(player.userId, player.gameId, player.score, player.date) ) ) }
串行执行方案(性能低,仅作参考)
如果你确实需要严格按顺序执行,不要用map,改用for...of循环即可:
const allPlayers = await Player.find(); for (const player of allPlayers) { await updateGameScore(player.userId, player.gameId, player.score, player.date); }
该方案处理5万条数据速度极慢,仅适合小批量数据场景,不推荐生产使用。
内容的提问来源于stack exchange,提问作者Tharinda Hashen
相关产品推荐
相关产品推荐

