JavaScript中含异步操作的forEach循环结束后如何执行后续代码
问题原因
打印得到空对象{}的核心是异步执行时序错误,和score对象的作用域无关:
- 外层数据库查询拿到赛事ID列表后,
forEach遍历触发的所有内层积分查询都是异步IO操作,会被放入事件队列等待执行,不会阻塞后续同步代码运行 - 你写的
console.log(score)属于同步代码,会在所有内层查询回调执行前就直接运行,此时score还未写入任何数据,自然输出空对象 - 你之前尝试Promise方案未生效,核心是没有将内层所有积分查询的异步操作纳入Promise等待链路,仅包装外层查询无法感知内层异步任务的执行状态
- 原代码还存在逻辑bug:积分累加的判断条件写为
score[element["points"]] != undefined,判断对象错误,就算时序正确也无法正常完成积分累加
修复实现
不要用普通forEach处理需要等待异步回调的遍历逻辑,将每个数据库查询包装为Promise,通过Promise.all等待所有异步查询、积分累加操作全部完成后,再执行后续逻辑,修复后代码如下:
async driversBySeason(season) { const score = {}; // 查询指定赛季所有赛事ID const raceIdQuery = `SELECT raceId FROM races WHERE year = ?`; const [raceQueryErr, raceList] = await new Promise(resolve => { // 用预处理传参避免SQL注入 this.con.query(raceIdQuery, [season], (err, res) => resolve([err, res])); }); if (raceQueryErr) { console.error(raceQueryErr); return score; } // 收集所有赛事的积分查询异步任务 const pointQueryTasks = raceList.map(raceItem => { const pointQuery = `SELECT driverId, points FROM results WHERE raceId = ?`; return new Promise(resolve => { this.con.query(pointQuery, [raceItem.raceId], (err, resultList) => { if (err) { console.error(err); resolve(); return; } // 累加当前赛事各车手积分 resultList.forEach(resItem => { const driverId = resItem.driverId; const addPoints = Number(resItem.points) || 0; if (score[driverId] === undefined) { score[driverId] = addPoints; } else { score[driverId] += addPoints; } }); resolve(); }); }); }); // 等待所有积分查询、累加操作全部执行完成 await Promise.all(pointQueryTasks); // 此处即可拿到完整计算结果 console.log(score); return score; }
性能优化建议
循环内发起多条单数据查询的IO开销很高,完全可以通过单条联表SQL直接算出所有车手的总积分,省去Node层循环遍历累加的逻辑,性能更高代码更简洁,参考SQL如下:
SELECT driverId, SUM(points) AS totalPoints FROM results WHERE raceId IN (SELECT raceId FROM races WHERE year = ?) GROUP BY driverId
使用该SQL时直接传入赛季参数,拿到的结果就是每个车手对应总积分的结构化数据,不需要额外做内存累加。
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

