NodeJS中如何从MySQL异步加载数据到User类对象并避免竞态条件
问题本质
你遇到的是Node.js异步IO的典型执行顺序问题,而非竞态条件:db.query默认是回调式异步API,调用后不会阻塞主线程后续代码执行,程序会先执行db.end()和return true逻辑,等数据库IO完成返回结果后,才会执行回调内的属性赋值代码,这就是你拿到返回值后属性还是undefined的原因。
解决方案
可以通过async/await完美解决该问题,主流的MySQL驱动(mysql、mysql2)均支持promise适配,改造方案如下:
注意事项
- 原有代码中每次调用
Load都执行db.connect()和db.end()的逻辑不合理,频繁创建销毁数据库连接会严重降低性能,推荐使用数据库连接池复用连接 - 直接拼接SQL字符串存在SQL注入风险,必须使用参数化查询传递查询条件
改造后的代码示例
1. 数据库配置(db.js,使用mysql2的promise API,推荐)
const mysql = require('mysql2/promise'); // 创建数据库连接池 const pool = mysql.createPool({ host: '数据库主机地址', user: '数据库用户名', password: '数据库密码', database: '数据库名称', connectionLimit: 10 }); module.exports = pool;
2. User类改造
const db = require("./db"); class User { constructor(loader){ this.id = loader?.id; this.email = loader?.email; this.name = loader?.name; } // 添加async修饰符,方法返回Promise对象 async Load(id = null) { // 使用参数化查询避免SQL注入 const sql = `select usrID, usrEmail, usrName from tblUsers where usrEmail = ?`; let connection; try { // 从连接池获取连接 connection = await db.getConnection(); // await等待查询结果返回 const [results] = await connection.query(sql, [id]); if (results.length === 0) { // 未查询到对应用户返回false return false; } // 赋值属性 this.id = results[0].usrID; this.email = results[0].usrEmail; this.name = results[0].usrName; return true; } catch (error) { throw error; } finally { // 释放连接回连接池 if (connection) connection.release(); } } } // 异步方法调用需要使用await,在顶层调用可以用立即执行async函数包裹 (async () => { const Robbie = new User(); if(await Robbie.Load("rob@example.com")){ // 此时属性已经完成赋值,可以正常打印 console.log("Robbie's ID is", Robbie.id); } })();
兼容旧mysql库的方案
如果你使用的是旧版本的mysql库不想更换为mysql2,可以使用Node.js内置的util.promisify将回调式API转换为promise版本:
const db = require("./db"); const { promisify } = require('util'); // 将query方法包装为返回promise的函数 db.query = promisify(db.query); // 后续User类的async/await写法和上述示例一致
内容的提问来源于stack exchange,提问作者RobbieS
相关产品推荐
相关产品推荐

