跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

JavaScript 异步操作中如何确保数据库插入顺序执行?

本文详解 node . js 中 mysql 查询的异步特性导致的执行时序问题,通过回调嵌套与错误处理,确保 `updatescratchergamedata` 完成后再执行 `updatescratcherrowdata`,避免因外键未生成而引发的 `cannot read properties of undefined (reading 'game_id')` 错误。 JavaScript 默认以 非阻塞、异步方式 执行 I/O 操作(如数据库查询),这意味着 connection.query() 调用后会立即返回,后续代码(如 updateScratcherRowData(cleanData))不会等待查询完成就继续执行。这正是你遇到问题的根本原因:updateScratcherRowData 在 INSERT INTO game 尚未真正写入数据库前就发起 SELECT game_id 查询,结果 rows[0] 为 undefined,从而触发 TypeError。 ✅ 正确做法:在插入完成的回调中触发下一流程 必须将 updateScratcherRowData(cleanData) 的调用 严格置于 INSERT 成功执行的回调内部 ,确保数据已落库且可被后续查询命中。参考修正后的代码:
function updateScratcherGameData(cleanData) { uniqueGameData.forEach(e => { // 推荐使用 forEach 替代 map(无返回值意图) const insertGameData = `INSERT INTO game (name, scratcher_lotto_id, gametype_id, active) VALUES ('${e.name}', ${e.scratcher_id}, 1, 1);`; connection.query(`SELECT name FROM game WHERE name = '${e.name}';`, (err, rows) => { if (err) throw err; if (rows.length === 0) { // 关键:INSERT 完成后再调用下游函数 connection.query(insertGameData, (err, result) => { if (err) { console.error('Failed to insert game:', err); throw err; } console.log(`Inserted game: ${e.name}`); }); } }); }); // ❌ 错误:此处调用仍属“同步位置”,无法保证所有 INSERT 已完成 // updateScratcherRowData(cleanData); }
但注意:上述写法仍存在 并发竞态风险 ——多个 connection.query(insertGameData, ...) 并行执行,updateScratcherRowData 无法知道 所有插入是否全部结束 。更健壮的方案是: ✅ 推荐进阶方案:使用 Promise + async/await(现代标准) 首先封装 connection.query 为 Promise 版本: Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 立即学习 “ Java免费学习笔记(深入) ”;
const util = require('util'); const queryAsync = util.promisify(connection.query).bind(connection);
然后重写逻辑,确保 串行插入 + 统一完成后调用下游 :
async function updateScratcherGameData(cleanData) { for (const e of uniqueGameData) { try { const [existing] = await queryAsync(`SELECT name FROM game WHERE name = ?`, [e.name]); if (existing.length === 0) { await queryAsync( `INSERT INTO game (name, scratcher_lotto_id, gametype_id, active) VALUES (?, ?, 1, 1)`, [e.name, e.scratcher_id] ); console.log(`Inserted new game: ${e.name}`); } } catch (err) { console.error(`Error inserting game ${e.name}:`, err); throw err; } } // ✅ 所有游戏插入确认完成,再执行关联数据插入 await updateScratcherRowData(cleanData); } async function updateScratcherRowData(cleanData) { for (const e of cleanData) { try { const [rows] = await queryAsync( `SELECT game_id FROM game WHERE name = ? AND scratcher_lotto_id = ?`, [e.name, e.scratcher_id] ); if (!rows || rows.length === 0) { throw new Error(`Game not found for name=${e.name}, id=${e.scratcher_id}`); } await queryAsync( `INSERT INTO scratcher_data (scrape_date, game_id, prize, odds, remaining_prize, total_prize) VALUES (NOW(), ?, ?, ?, ?, ?)`, [ rows[0].game_id, e.prize, e.odds, e.tickets_left, e.tickets_remaining ] ); console.log(`Inserted scratcher data for ${e.name}`); } catch (err) { console.error(`Failed to insert scratcher row for ${e.name}:`, err); throw err; } } }
⚠️ 重要注意事项 SQL 注入风险 :原始代码直接拼接字符串(如 '${e["name"]}')极易遭受 SQL 注入攻击。✅ 务必改用参数化查询(如 ? 占位符 + 参数数组),如上例所示。 事务保障 :若需强一致性(例如:某游戏插入失败则整批回滚),应包裹在 START TRANSACTION / COMMIT / ROLLBACK 中。 性能权衡 :async/await 串行执行较慢但逻辑清晰;如需高性能,可用 Promise.all() 并行插入,但需配合 Promise.allSettled() 或自定义完成计数器确保下游触发时机。 通过理解异步本质、合理组织回调或采用 Promise/async-await,即可彻底解决“外键未就绪”的时序问题,让数据流真正按业务逻辑顺序可靠执行。

相关文章