Promise化IndexedDB事务操作无法回滚问题求助
你遇到的核心问题是 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抛出异常时:
executorPromise会被 reject- 我们调用
tx.abort(),触发事务回滚 - 所有在事务中已经执行的操作(比如
_deleteDamage、_copyObject)都会被撤销 - 最终
transaction返回的 Promise 会被 reject,外部可以捕获这个错误
内容的提问来源于stack exchange,提问作者Vojtech

