React Native中tx.executeSql异步函数循环变量异常问题求助
问题分析与解决方案
你遇到的是闭包陷阱导致的异步回调变量捕获问题,同时代码里的await也没有真正等待所有数据库插入操作完成,我来一步步帮你解决:
核心问题:循环变量的闭包陷阱
for...in循环中的变量i你甚至没加var/let/const声明,会变成全局变量,本质上它是在循环外部作用域声明的,当异步的tx.executeSql回调执行时,循环已经全部跑完,i已经指向数组的最后一个索引3,所以所有回调里拿到的都是同一个i值。
另外,db.transaction本身不返回Promise,你用await它其实不会等待内部所有executeSql完成,这也是为什么控制台里"事务结束"、"insertarDatos执行完毕"会在回调打印之前出现。
解决方案
方案1:使用for...of循环(推荐)
for...of会为每次迭代创建独立的块级作用域,确保回调能捕获到当前迭代的正确索引/值,同时我们把transaction包装成Promise,让await能真正等待所有操作完成:
insertarDatos = async () => { console.log('开始执行insertarDatos'); // 把transaction包装成Promise,让await能等待所有操作完成 await new Promise((resolve, reject) => { db.transaction((tx) => { console.log('开始事务'); // 用for...of遍历数组,拿到每个元素和索引 for (const [i, usuario] of this.state.dataUsuarios.entries()) { // 改用参数化SQL,避免注入风险 const sql = `INSERT INTO usuarios VALUES (?, ?, ?, ?, ?, ?)`; console.log('执行前i的值为: ' + i); tx.executeSql(sql, [usuario.id, usuario.nombre, usuario.usuario, usuario.password, usuario.grupo, usuario.faenas], (tx, results) => { console.log('executeSql回调中i的值为: ' + i); if (results.rowsAffected > 0) { console.log('数据已插入数据库!'); } else { console.log('无法插入数据到数据库!'); } }, (error) => { console.log(error.message); reject(error); // 捕获错误,终止Promise }); } console.log('事务结束'); }, (error) => { console.log('事务失败:', error.message); reject(error); }, () => { // 事务成功完成时resolve resolve(); }); }); console.log('insertarDatos执行完毕'); };
方案2:使用Array.prototype.forEach
forEach的回调函数会为每个元素创建独立的作用域,天然避免闭包陷阱,同样搭配Promise包装来实现真正的等待:
insertarDatos = async () => { console.log('开始执行insertarDatos'); await new Promise((resolve, reject) => { db.transaction((tx) => { console.log('开始事务'); this.state.dataUsuarios.forEach((usuario, i) => { const sql = `INSERT INTO usuarios VALUES (?, ?, ?, ?, ?, ?)`; console.log('执行前i的值为: ' + i); tx.executeSql(sql, [usuario.id, usuario.nombre, usuario.usuario, usuario.password, usuario.grupo, usuario.faenas], (tx, results) => { console.log('executeSql回调中i的值为: ' + i); if (results.rowsAffected > 0) { console.log('数据已插入数据库!'); } else { console.log('无法插入数据到数据库!'); } }, (error) => { console.log(error.message); reject(error); }); }); console.log('事务结束'); }, (error) => { console.log('事务失败:', error.message); reject(error); }, () => { resolve(); }); }); console.log('insertarDatos执行完毕'); };
方案3:修复for...in的作用域问题(适合理解闭包原理)
在for...in里用let声明i,让每次循环都绑定当前的i值:
insertarDatos = async () => { console.log('开始执行insertarDatos'); await new Promise((resolve, reject) => { db.transaction((tx) => { console.log('开始事务'); // 用let声明i,每次循环绑定当前值 for (let i in this.state.dataUsuarios) { const usuario = this.state.dataUsuarios[i]; const sql = `INSERT INTO usuarios VALUES (?, ?, ?, ?, ?, ?)`; console.log('执行前i的值为: ' + i); tx.executeSql(sql, [usuario.id, usuario.nombre, usuario.usuario, usuario.password, usuario.grupo, usuario.faenas], (tx, results) => { console.log('executeSql回调中i的值为: ' + i); if (results.rowsAffected > 0) { console.log('数据已插入数据库!'); } else { console.log('无法插入数据到数据库!'); } }, (error) => { console.log(error.message); reject(error); }); } console.log('事务结束'); }, (error) => { console.log('事务失败:', error.message); reject(error); }, () => { resolve(); }); }); console.log('insertarDatos执行完毕'); };
额外重要提醒:避免SQL注入
你原来的代码直接把变量拼接到SQL语句里,存在严重的SQL注入风险,上面的方案都改成了参数化查询,用?作为占位符,把变量放在第二个参数数组里,这样既安全又能避免字符串转义的问题。
内容的提问来源于stack exchange,提问作者Braven
相关产品推荐
相关产品推荐

