IndexedDB 事务在异步回调里为啥会失效?
我在一个 fetch 请求的 then 回调里打开 IndexedDB 事务,想把返回的数据存进去,但总是报错说事务已经结束。明明代码是按顺序写的啊?
我试过把 db.transaction 放在回调外面,但又拿不到数据。难道 IndexedDB 的事务不能在异步操作里用?
fetch('/api/data')
.then(res => res.json())
.then(data => {
const tx = db.transaction('items', 'readwrite');
const store = tx.objectStore('items');
store.put(data); // 这里报错:TransactionInactiveError
});
具体来说,当你调用
db.transaction()时,事务对象会立即被创建,但只要你代码的执行权交给浏览器(比如进入下一个 .then 回调),事务就会进入非活跃状态。一旦事务变成非活跃状态,任何数据库操作都会触发 TransactionInactiveError。你代码的问题在于:
store.put(data)虽然写在 .then 回调里,但这个回调本身是被异步调用的。当浏览器执行这个回调时,外层的事务可能已经不在活跃状态了。正确的做法是:在事务创建的同一个同步代码块中完成所有数据库操作。把 fetch 放在外层,获取到数据后在同步代码里立即执行事务。
来看修复后的代码:
如果你的数据比较复杂,需要存储多条记录,可以这样写:
另外提一下,IndexedDB 的每个操作(get、put、add 等)都会返回一个请求对象,如果你需要知道操作是否成功,应该监听这个请求的 onsuccess/onerror,而不是事务的。事务的 oncomplete 表示所有操作都发送成功了,但不代表每个操作都真的写入了。
如果你确实需要在数据库操作之后做某些异步的事情,正确的方式是:
记住一个原则:IndexedDB 事务是同步创建、异步执行的,但所有数据库操作必须在事务创建的同步上下文中立即发起。这就是为什么它和你习惯的 async/await 写法不太一样。