You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:05:23