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

如何在使用sql.js和IndexedDB的Web应用中确保SQLite数据库完成初始化后再进行交互

如何在使用sql.js和IndexedDB的Web应用中确保SQLite数据库完成初始化后再进行交互

我完全懂你遇到的这个头疼问题——用sql.js结合IndexedDB做浏览器端的SQLite存储时,经常因为数据库还没彻底完成加载和初始化,就着急执行SQL操作,结果抛出“Database is not initialized”的错误。咱们一步步来把这个问题解决掉。

首先,先分析下你当前代码里的核心问题:虽然你用了async/await来加载数据库,也调用了initializeDatabase(),但表单提交的事件监听是直接绑定的,没有和初始化流程做强关联。哪怕你加了if (!db)的判断,也可能因为异步操作的竞态条件(比如用户快速点击提交,刚好在初始化的中间步骤触发),导致要么db还没赋值,要么表还没创建完成,进而报错。

接下来是具体的解决方案,我会基于你的代码做针对性修改:


1. 用全局Promise跟踪初始化状态

咱们创建一个dbReadyPromise变量,专门用来标记数据库是否完全初始化完成(包括加载数据、创建必要的表)。所有需要操作数据库的代码,都必须等待这个Promise完成后再执行,从根源上避免竞态问题。

2. 重构数据库加载与初始化流程

调整loadDatabaseFromIndexedDB,让它返回数据库实例而不是直接修改全局变量,这样数据流更清晰;同时修改initializeDatabase,让它负责完整的初始化流程并关联到dbReadyPromise。

修改后的完整代码

JavaScript部分

let db;
const dbName = "db_transactions.db";
let dbReadyPromise; // 全局Promise,标记数据库是否完全就绪

// Function to load the database from IndexedDB
async function loadDatabaseFromIndexedDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sqliteDBs', 1);
    request.onupgradeneeded = () => {
      request.result.createObjectStore('databases', { keyPath: 'name' });
    };

    request.onsuccess = () => {
      const transaction = request.result.transaction('databases', 'readwrite');
      const dbStore = transaction.objectStore('databases');
      const getRequest = dbStore.get(dbName);

      getRequest.onsuccess = () => {
        const data = getRequest.result;
        const loadedDb = data 
          ? new SQL.Database(new Uint8Array(data.dbBinary)) 
          : new SQL.Database();
        resolve(loadedDb);
      };
      getRequest.onerror = reject;
    };

    request.onerror = reject;
  });
}

// Function to save the database to IndexedDB
async function saveDatabaseToIndexedDB(db) {
  const binaryData = db.export();
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sqliteDBs', 1);
    request.onupgradeneeded = () => {
      request.result.createObjectStore('databases', { keyPath: 'name' });
    };

    request.onsuccess = () => {
      const transaction = request.result.transaction('databases', 'readwrite');
      const dbStore = transaction.objectStore('databases');
      const putRequest = dbStore.put({ name: dbName, dbBinary: binaryData });

      putRequest.onsuccess = resolve;
      putRequest.onerror = reject;
    };

    request.onerror = reject;
  });
}

// Function to initialize the database
async function initializeDatabase() {
  try {
    db = await loadDatabaseFromIndexedDB();
    console.log("Database loaded from IndexedDB or created.");

    // 创建必要的表,确保这一步完成后才标记初始化完成
    db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);");
    console.log("Table 'users' created or already exists.");
    return db;
  } catch (err) {
    console.error("Failed to initialize database:", err);
    throw err; // 抛出错误,让Promise处于rejected状态
  }
}

// 初始化就绪Promise
dbReadyPromise = initializeDatabase().then(() => {
  console.log("Database is fully initialized and ready for use.");
}).catch(err => {
  console.error("Database initialization failed:", err);
});

// Handle form submission
document.getElementById('dataForm').addEventListener('submit', async function(event) {
  event.preventDefault();

  try {
    // 强制等待数据库完全初始化完成,不管用户操作多快
    await dbReadyPromise;
  } catch (err) {
    console.error("Database initialization failed, cannot proceed:", err);
    // 这里可以给用户显示页面提示,比如alert或者页面上的错误信息
    return;
  }

  const name = document.getElementById('name').value;
  const email = document.getElementById('email').value;

  try {
    db.run("INSERT INTO users (name, email) VALUES (?, ?);", [name, email]);
    await saveDatabaseToIndexedDB(db);
    console.log('Database saved to IndexedDB.');

    const result = db.exec("SELECT * FROM users;");
    document.getElementById('output').textContent = JSON.stringify(result, null, 2);
  } catch (err) {
    console.error("Error while inserting data or saving database:", err);
  }
});

HTML部分(与原代码一致)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SQLite with WebAssembly</title>
  <script src="https://cdn.jsdelivr.net/npm/sql.js@1.6.0/dist/sql-wasm.js"></script>
</head>

<body>
  <h1>SQLite with WebAssembly (Persistent Database)</h1>

  <form id="dataForm">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name" required><br><br>

    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required><br><br>

    <button type="submit">Submit</button>
  </form>

  <h2>Database Output</h2>
  <pre id="output"></pre>
</body>

</html>

为什么这样修改能解决问题?

  • dbReadyPromise确保所有操作都等待初始化完成:不管用户多快点击提交,表单处理代码都会先等待数据库加载、表创建的整个流程完成,彻底避免了“未初始化”的错误。
  • 清晰的数据流:把全局db的赋值从loadDatabaseFromIndexedDB内部移到initializeDatabase中,避免了异步操作中全局变量的意外状态。
  • 完善的错误处理:初始化失败时,所有后续操作都会捕获到错误,不会默默失败,你还可以扩展这部分给用户显示友好的提示。

额外优化建议

  • 封装IndexedDB连接:你可以把indexedDB.open的逻辑封装成一个单独的函数,避免在load和save函数中重复写相同的代码。
  • 用户友好的错误提示:比如在初始化失败时,在页面上显示“数据库初始化失败,请刷新页面重试”的提示,而不只是在控制台打印。
  • 事务优化:在IndexedDB操作中,确保事务正确关闭,或者考虑用Promise封装的IndexedDB库来简化代码(不过原生API也足够用)。

备注:内容来源于stack exchange,提问作者Joey Cadieux

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 18:25:31