Expo SQLite 持续出现NativeDatabase.execAsync未处理Promise拒绝问题排查
Expo SQLite 未处理Promise拒绝问题排查与解决
问题现象
每次刷新应用时,都会收到以下警告:
Possible unhandled promise rejection (id: X): Error: Call to function 'NativeDatabase.execAsync' has been rejected. -> Caused by: java.lang.NullPointerException: java.lang.NullPointerException
警告会持续出现,且id每次递增(如id:1、id:2等)。
相关代码
import * as SQLite from 'expo-sqlite'; import { useEffect } from 'react'; async function createLocalDatabase() { const db = await SQLite.openDatabaseAsync("orders.db"); await db.execAsync(` PRAGMA journal_mode = WAL; CREATE TABLE IF NOT EXISTS order_header ( id INTEGER PRIMARY KEY AUTOINCREMENT, firm TEXT NOT NULL, shop TEXT NOT NULL, delivery_date DATE NOT NULL, notice TEXT, counterparty_id INTEGER ); CREATE TABLE IF NOT EXISTS order_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, item_name TEXT NOT NULL, sale_unit TEXT NOT NULL, quantity REAL NOT NULL, order_header_id INTEGER NOT NULL, FOREIGN KEY (order_header_id) REFERENCES order_header (id) ); `); } useEffect(() => { createLocalDatabase(); }, []); async function insertOrderHeader(db, firm, shop, deliveryDate, notice, counterpartyId) { const statement = await db.prepareAsync( "INSERT INTO order_header (firm, shop, delivery_date, notice, counterparty_id) VALUES (?, ?, ?, ?, ?)" ); try { const result = await statement.executeAsync([ firm, shop, deliveryDate, notice, counterpartyId, ]); return result.lastInsertRowId; } finally { await statement.finalizeAsync(); } } async function insertOrderItem( db, orderHeaderId, itemName, saleUnit, quantity ) { const statement = await db.prepareAsync( "INSERT INTO order_items (order_header_id, item_name, sale_unit, quantity) VALUES (?, ?, ?, ?)" ); try { await statement.executeAsync([ orderHeaderId, itemName, saleUnit, quantity, ]); } finally { await statement.finalizeAsync(); } } async function insertOrderWithItems( firm, shop, deliveryDate, notice, orderItems ) { const db = await SQLite.openDatabaseAsync("orders.db"); try { await db.execAsync("BEGIN TRANSACTION"); const orderHeaderId = await insertOrderHeader( db, firm, shop, deliveryDate, notice ); for (const item of orderItems) { await insertOrderItem( db, orderHeaderId, item.name, item.saleUnit, item.quantity ); } await db.execAsync("COMMIT"); console.log( "Order with items inserted successfully. Order ID:", orderHeaderId ); return orderHeaderId; } catch (error) { await db.execAsync("ROLLBACK"); console.error("Error inserting order with items:", error); throw error; } }
已尝试措施
- 确保所有SQLite操作都包裹在try/catch块中
- 所有异步SQLite操作都使用await
- 为订单插入操作实现了事务处理
疑问与解答
1. 为何会持续收到未处理Promise拒绝警告?
核心原因有两个:
createLocalDatabase是异步函数,在useEffect中直接调用时,其返回的Promise没有错误捕获逻辑,一旦初始化过程中出现异常(如数据库打开失败、SQL语句执行出错),就会触发未处理Promise拒绝。insertOrderWithItems调用insertOrderHeader时少传了counterpartyId参数,导致插入order_header时参数数量不匹配,触发SQL执行错误;同时初始化阶段的错误未被捕获,持续触发警告。
2. 警告的id为何每次刷新都会递增?
每次刷新应用,useEffect都会重新执行createLocalDatabase,每次执行都会生成一个新的Promise实例。如果这个Promise的rejection始终未被捕获,系统会为每个未处理的拒绝分配一个递增的id,因此每次刷新id都会加1。
3. 如何正确处理或解决该问题?
按以下步骤修复:
- 给数据库初始化添加错误捕获:修改
useEffect中的调用逻辑,捕获初始化错误:
或直接在useEffect(() => { (async () => { try { await createLocalDatabase(); } catch (error) { console.error("Database initialization failed:", error); } })(); }, []);createLocalDatabase内部添加try/catch:async function createLocalDatabase() { try { const db = await SQLite.openDatabaseAsync("orders.db"); await db.execAsync(` PRAGMA journal_mode = WAL; CREATE TABLE IF NOT EXISTS order_header ( id INTEGER PRIMARY KEY AUTOINCREMENT, firm TEXT NOT NULL, shop TEXT NOT NULL, delivery_date DATE NOT NULL, notice TEXT, counterparty_id INTEGER ); CREATE TABLE IF NOT EXISTS order_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, item_name TEXT NOT NULL, sale_unit TEXT NOT NULL, quantity REAL NOT NULL, order_header_id INTEGER NOT NULL, FOREIGN KEY (order_header_id) REFERENCES order_header (id) ); `); } catch (error) { console.error("Failed to create database:", error); } } - 修复参数缺失问题:在
insertOrderWithItems中调用insertOrderHeader时补充counterpartyId参数(允许为空则传null):const orderHeaderId = await insertOrderHeader( db, firm, shop, deliveryDate, notice, null // 或传入实际的counterpartyId值 ); - 校验SQL语句正确性:确认
execAsync中的SQL语句无语法错误,比如WAL模式是否在当前设备上支持,表结构定义是否符合SQLite规范。
4. 数据库初始化或使用方式是否存在问题?
存在以下问题:
- 初始化函数
createLocalDatabase无错误处理机制,执行失败会直接抛出未被捕获的Promise拒绝。 insertOrderWithItems中调用insertOrderHeader时参数不完整,违反SQL语句的参数要求,会导致插入失败。- 数据库连接未复用,每次插入订单都会重新打开连接,虽Expo SQLite内部有连接池处理,但频繁开关仍可能影响性能,建议初始化时保留数据库实例复用。
环境信息
- Expo版本:~51.0.18
- expo-sqlite版本:~14.0.5
- React Native版本:0.74.3
- 设备:Motorola E20
内容的提问来源于stack exchange,提问作者Kalin Toshev
相关产品推荐
相关产品推荐

