node-sqlite3中如何等待db.run()执行完成后运行后续操作
node-sqlite3 替代db.serialize()的顺序执行方案
node-sqlite3 原生API全部是异步回调风格,不加控制直接按同步顺序写代码,不会等待上一步操作落盘,才会出现建表未完成就触发插入的时序问题。除了官方提到的db.serialize()包裹方案,还有两种更灵活、代码结构更清爽的实现方式,不会造成多层嵌套的维护问题。
- 方案一:使用原生回调按顺序嵌套调用
所有db.run、db.prepare、stmt.run、stmt.finalize方法都支持传入回调作为最后一个入参,上一步操作执行完成(成功/报错)才会触发回调执行后续逻辑,示例:
db.run("CREATE TABLE lorem (info TEXT)", (createErr) => { if (createErr) throw createErr; const stmt = db.prepare("INSERT INTO lorem VALUES (?)", (prepErr) => { if (prepErr) throw prepErr; stmt.run("Ipsum", (runErr) => { if (runErr) throw runErr; stmt.finalize((finalErr) => { if (finalErr) throw finalErr; // 后续逻辑写在这里 }); }); }); });
这种方案的缺点是操作步骤多了会出现回调地狱,适合只有2-3步的简单场景。
- 方案二:Promise封装 + async/await 线性调用(推荐)
把原生回调API封装成Promise后,就可以用async/await写完全平铺的顺序代码,逻辑拆分也更方便,不会有嵌套臃肿的问题。
先做一层简单的通用封装:
// 封装通用run方法 const asyncRun = (sql, params = []) => new Promise((resolve, reject) => { db.run(sql, params, (err) => err ? reject(err) : resolve()); }); // 封装prepare方法,顺便把语句实例的方法也改成Promise版 const asyncPrepare = (sql) => new Promise((resolve, reject) => { const stmt = db.prepare(sql, (prepErr) => { if (prepErr) return reject(prepErr); stmt.asyncRun = (params) => new Promise((res, rej) => { stmt.run(params, (runErr) => runErr ? rej(runErr) : res()); }); stmt.asyncFinalize = () => new Promise((res, rej) => { stmt.finalize((finalErr) => finalErr ? rej(finalErr) : res()); }); resolve(stmt); }); });
业务代码直接用await按顺序写即可,自动等待上一步执行完成:
async function initTableAndData() { await asyncRun("CREATE TABLE lorem (info TEXT)"); const stmt = await asyncPrepare("INSERT INTO lorem VALUES (?)"); await stmt.asyncRun("Ipsum"); await stmt.asyncFinalize(); // 后续逻辑直接平铺,不需要嵌套在任何回调/serialize块里 } initTableAndData().catch(err => console.error("数据库操作异常:", err));
补充说明:5.0以上版本的node-sqlite3已经内置了Promise风格的API,不需要手动封装,初始化后直接调用db.run()、db.prepare()等方法不传回调时,会直接返回Promise,可以直接配合await使用。
注意:不要用固定时长的setTimeout延时等待操作完成,不同环境下数据库执行速度差异很大,延时设置过短依然会出现时序问题,设置过长会拖慢程序运行效率,完全不可靠。
内容的提问来源于stack exchange,提问作者Anna
相关产品推荐
相关产品推荐

