indexeddb事务必须在所有异步操作完成前保持活跃,否则自动中止;事务生命周期由idbrequest驱动,不能跨非idb异步边界,需确保所有操作在同一事务上下文中执行。

IndexedDB 事务必须在所有异步操作完成前保持活跃,否则会自动中止——这是绝大多数“事务已中止”错误的根本原因。
事务生命周期不能跨异步边界
IDB 事务是“请求驱动”的:只要当前事务内还有未完成的 IDBRequest(比如 get、put、openCursor 的返回值),事务就继续存活;一旦所有请求都 settled(成功或失败),事务立即提交或回滚。如果在事务中混入非 IDB 异步操作(如 setTimeout、fetch、Promise.then),后续的 IDB 操作就可能因事务已关闭而失败。
- ✅ 正确做法:所有数据库操作(读、写、游标遍历)都作为同一个事务的子请求发起,不跳出事务上下文
- ❌ 常见错误:在 request.onsuccess 里再调用另一个 objectStore.add(),但没确认该操作是否仍在原事务内
- ? 提示:可通过 transaction.active 属性实时判断事务是否仍有效,调试时可加断点检查
用 Promise 封装请求时务必绑定事务
原生 IndexedDB 使用事件回调,容易丢失上下文;改用 Promise 包装虽更简洁,但必须确保每个 Promise 都来自当前事务的对象存储,否则会隐式创建新事务(只读)或报错。
- ✅ 推荐写法:const store = transaction.objectStore('users'); return promisify(store.get(id));
- ❌ 危险写法:return db.transaction('users').objectStore('users').get(id) —— 新建了独立事务,与原事务无关
- ⚠️ 注意:Promise.all 并发多个 IDB 请求时,所有请求必须来自同一 transaction,否则可能触发并发冲突或静默失败
读写混合场景要明确事务类型
事务初始化时指定的 mode 决定了它能执行的操作范围。readwrite 事务支持读+写,但若只做读取,应使用 readonly —— 不仅语义清晰,还能避免因意外写入导致的异常中止。
- ✅ 批量更新+记录日志:用 db.transaction(['users', 'logs'], 'readwrite') 显式声明所有涉及的 objectStore
- ✅ 单纯查询列表:用 db.transaction(['users'], 'readonly'),提升并发性能
- ⚠️ 警惕:未在 transaction() 第二参数中列出的 objectStore,即使在代码里访问了,也会抛出 InvalidStateError
错误处理必须阻断后续操作
IDB 事务具有原子性:任一请求失败,默认中止整个事务。但如果你监听了某个 request.onerror 却没阻止后续请求,就可能造成部分写入、状态不一致。
- ✅ 标准模式:在 request.onerror 中调用 transaction.abort(),并统一 reject 或 dispatch 失败 action
- ✅ 游标遍历中出错:调用 cursor.continue() 前先检查 cursor,避免空 cursor 导致 Uncaught TypeError
- ? 实用技巧:给事务设置 transaction.onabort 和 transaction.oncomplete,作为整体生命周期钩子,用于清理或通知










