如何使用IndexedDB存储通过fetch获取的文件?解决事务失效报错
如何用IndexedDB存储通过fetch获取的文件?
需要下载一组文件并使用IndexedDB进行本地存储,当前使用fetch API的代码如下:
cacheRecordings() { var request = window.indexedDB.open("database", 2); request.onsuccess = event => { var database = event.target.result; var transaction = database.transaction(["store"], 'readwrite'); //second step is opening the object store this.objectStore = transaction.objectStore("store"); } for (const url of this.urls) { fetch(url) .then(resp => resp.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const index = this.objectStore.index('path'); index.openCursor().onsuccess = function(event) { <-- Error is thrown here this.objectStore.add(url, path); }.bind(this) }) .catch((error) => { console.log(error) }) } }
执行代码时出现以下错误:
Failed to execute 'openCursor' on 'IDBIndex': The transaction is not active.
Failed to execute 'index' on 'IDBObjectStore': The transaction has finished
曾找到相关问题,但不适用于fetch获取文件的场景:《TransactionInactiveError: 无法在IDBObjectStore上执行'add':事务未激活》
问题原因
IndexedDB的事务具有自动提交机制:当事务作用域内没有待处理的数据库请求时,事务会立即关闭。原代码中,request.onsuccess回调执行完毕后,事务就自动提交了;而fetch是异步操作,等fetch完成后再去操作objectStore时,事务已经处于非活跃状态,因此抛出错误。
另外,代码中使用window.URL.createObjectURL(blob)存储临时URL是不合理的——该URL仅在当前页面会话有效,页面刷新后就会失效,无法长期存储文件内容。
解决方案
将所有异步操作(fetch+数据库存储)放在事务的作用域内,使用async/await或Promise管理异步流程,确保事务在所有操作完成前保持活跃。同时直接存储Blob对象而非临时URL,保证文件可长期读取。
修正后的代码示例:
cacheRecordings() { const request = window.indexedDB.open("database", 2); request.onsuccess = async (event) => { const db = event.target.result; // 创建读写事务,作用域覆盖所有文件处理逻辑 const transaction = db.transaction(["store"], 'readwrite'); const objectStore = transaction.objectStore("store"); // 监听事务全局状态 transaction.oncomplete = () => { console.log("所有文件已成功存储到IndexedDB"); }; transaction.onerror = (err) => { console.error("事务执行失败:", err); }; try { // 并行处理所有文件的下载与存储 await Promise.all(this.urls.map(async (fileUrl) => { const resp = await fetch(fileUrl); const blob = await resp.blob(); // 用文件URL的路径作为主键(可根据需求自定义唯一标识) const fileKey = new URL(fileUrl).pathname; // 先检查文件是否已存在,避免重复存储 const existingFile = await objectStore.get(fileKey); if (!existingFile) { await objectStore.add(blob, fileKey); } else { console.log(`文件 ${fileKey} 已存在,跳过存储`); } })); } catch (err) { console.error("文件处理出错:", err); // 发生异常时中止事务,回滚所有操作 transaction.abort(); } }; request.onerror = (err) => { console.error("打开数据库失败:", err); }; }
关键优化点
- 事务作用域覆盖异步逻辑:将fetch和数据库操作全部放在
request.onsuccess回调内部的事务作用域中,确保事务在所有异步操作完成前不会自动提交。 - 直接存储Blob:Blob是文件的二进制对象,可长期保存在IndexedDB中,避免临时URL失效问题。
- 高效的存在性检查:使用
objectStore.get()代替游标查询,直接通过主键检查文件是否已存在,提升性能。 - 并行处理与错误控制:用
Promise.all并行处理多个文件的下载和存储,同时通过try/catch捕获异常,必要时中止事务回滚操作。 - 事务状态监听:通过
transaction.oncomplete和transaction.onerror监听事务的全局完成/失败状态,便于统一处理结果。
内容的提问来源于stack exchange,提问作者etayluz
相关产品推荐
相关产品推荐

