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

Promise化IndexedDB事务操作无法回滚问题求助

问题根源:IndexedDB 事务与 Promise 的异步生命周期不匹配

你遇到的核心问题是 IndexedDB 事务会在事务回调函数(也就是你的executorFnc)执行完毕后立即尝试提交,而不是等待回调内部的 Promise 链完成。这就导致你的then里的操作其实已经不在原事务上下文里了——事务早已经提交,后续操作失败自然无法回滚之前的变更。

举个直观的例子:你的executorFnc返回了一个 Promise,但 IndexedDB 并不会等待这个 Promise 决议。当executorFnc执行到return Promise.all(copyPromises).then(...)这一行时,函数本身已经执行完毕,IndexedDB 就认为事务可以提交了,而此时你的后续保存、删除操作还在异步执行,相当于在一个已经提交的事务里做操作(甚至是在新的隐式事务里),自然无法触发回滚。

解决方案:让事务等待 Promise 链完成

你需要修改你的transaction方法,让它返回一个 Promise,并且明确等待executorFnc返回的 Promise 完成后,再处理事务的状态。同时要正确绑定事务的onabort、oncomplete事件,确保事务的生命周期和 Promise 链同步。

修改后的transaction方法如下:

transaction(mode, storeNames, executorFnc) {
  return new Promise((resolve, reject) => {
    const tx = this._db.transaction(this._transactionStoreNames(storeNames), this._transactionMode(mode));
    
    // 监听事务 abort 事件,直接 reject Promise
    tx.onabort = (event) => {
      reject(new Error(`Transaction failed: ${event.target.errorCode}`));
    };
    
    // 监听事务完成事件,在 executor 完成后 resolve
    tx.oncomplete = () => {
      resolve();
    };
    
    try {
      // 执行回调,获取返回的 Promise
      const executorPromise = executorFnc(tx);
      
      // 等待 executor 的 Promise 完成,如果它失败了,主动 abort 事务
      executorPromise
        .then(resolve)
        .catch((error) => {
          // 主动终止事务,触发回滚
          tx.abort();
          reject(error);
        });
    } catch (error) {
      // 同步错误也需要 abort 事务
      tx.abort();
      reject(error);
    }
  });
}

为什么这样改?

  • 我们把transaction包装成了一个 Promise,让外部可以正确等待整个事务流程完成。
  • 当executorFnc返回的 Promise 被 reject(比如_saveObject抛出异常),我们会主动调用tx.abort(),强制事务回滚所有之前的变更。
  • 事务的oncomplete和onabort事件会和 Promise 链的状态联动,确保只有当所有异步操作完成后,事务才会提交或终止。

额外注意:修复_saveObject的已知异常

虽然你提到已经知道如何修复这个异常,但还是补充一句:你遇到的Failed to execute 'put' on 'IDBObjectStore': The object store uses in-line keys and the key parameter was provided错误,是因为你的对象仓库使用了内联键(创建仓库时指定了keyPath),此时调用put不能手动传第二个id参数。你可以这样修改_saveObject:

_saveObject(tx, storeName, object, id) {
  return new Promise((resolve, reject) => {
    const store = tx.objectStore(storeName);
    // 如果是内联键仓库,只传 object 即可
    const result = store.keyPath ? store.put(object) : store.put(object, id);
    result.onsuccess = (event) => resolve(event.target.result);
    result.onerror = (event) => reject(event.target.error);
  });
}

验证逻辑

修改后,当_saveObject抛出异常时:

  1. executorPromise会被 reject
  2. 我们调用tx.abort(),触发事务回滚
  3. 所有在事务中已经执行的操作(比如_deleteDamage、_copyObject)都会被撤销
  4. 最终transaction返回的 Promise 会被 reject,外部可以捕获这个错误

内容的提问来源于stack exchange,提问作者Vojtech

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:37:51