IndexedDB执行update/put操作未返回预期状态更新对象的问题排查
问题1:Chrome DevTools IDB结构解读与更新失效原因
Chrome IDB面板结构说明
你看到的三个区域的差异本质是内存临时修改的对象和实际持久化存储的对象不一致:
- 你标记的A、C区域显示的是JS运行内存中修改后的变量值,比如你给查询返回的对象加了
gameID属性,console.log自然会显示修改后的值 - B区域是IDB持久化存储的实际结构,你修改的属性没有被正确写到需要存储的比赛对象上,所以持久化后的数据还是旧值
更新失效的核心原因
3个代码错误直接导致更新不生效:
- 新增比赛时错误传入数组而非对象:你
addGameIDB里的newGame用了[]包裹,实际传给gameStore.add()的是一个数组对象。而你的gamesList仓库keyPath配置为gameID,IDB自动生成的主键只会写在你传入的根对象(也就是这个数组)的gameID属性上,不会写到数组内部的比赛对象上。 - 更新对象层级错误:你查询返回的记录是数组,给外层数组加
gameID属性,数组内部的比赛对象本身没有被修改,put回IDB后,内部比赛对象的gameID自然还是初始的null。 - 细节语法错误:
- 游标更新逻辑里
cursor.continue没有加括号,写成了cursor.continue;,游标只会执行一次不会遍历所有记录 putGameID的resolve逻辑错误,你resolve(updateGameRequest.onsuccess)是返回了事件函数本身,没有等待事务完成就提前结束了Promise,可能出现事务还没提交就执行后续逻辑的情况- 获取对象仓库时传了数组参数:
tx.objectStore(['gamesList']),正确传参应为字符串tx.objectStore('gamesList')
- 游标更新逻辑里
问题2:调整方案实现需求
其实你完全不需要额外做回写更新的操作,IDB在keyPath模式下开启autoIncrement时,只要你传入的对象keyPath对应字段为null/undefined,自动生成的主键会直接写到该字段上,你只要修正代码错误即可:
第一步:修正新增比赛的代码
删掉newGame外层的数组包裹,直接传入对象即可,此时插入成功后返回的对象已经自动填充了gameID字段:
function addGameIDB(conn) { return new Promise((resolve, reject) => { // 原有日期格式化等逻辑保留 // 去掉外层[],直接传入对象 let newGame = { gameID: null, // 留空即可,IDB会自动填充 gameDate: thisGameDate, gameTime: thisGameTime, team1Name: thisTeamOne, team2Name: thisTeamTwo, gameCompleted: false, createdDate: d }; const tx = conn.transaction('gamesList','readwrite'); const gameStore = tx.objectStore('gamesList'); const gameRequest = gameStore.add(newGame); gameRequest.onsuccess = (ev) => { console.log('Successfully inserted an game object'); const newGameRequest = gameStore.get(gameRequest.result); newGameRequest.onsuccess = () => { // 此时返回的game对象已经自带自动生成的gameID resolve(newGameRequest.result); } }; gameRequest.onerror = (err) => { console.log('error attempting to insert game object' + err); reject(gameRequest.error); } // 监听事务完成,避免提前返回 tx.oncomplete = () => {}; tx.onerror = () => reject(tx.error); }); }
第二步:删掉多余的回写更新逻辑
修正新增代码后,不需要再调用putGameID做回写,直接就能拿到正确的gameID:
try { conn = await idbConnect(DBName,DBVersion); game = await addGameIDB(conn); globalGameID = game.gameID; // 直接取即可,不需要后续更新 console.log({globalGameID}); }
可选:原有更新逻辑的修正方案
如果你确实需要保留手动更新的逻辑,修正后的代码如下:
function putGameID (conn, thisGameID) { return new Promise((resolve, reject) => { const tx = conn.transaction('gamesList', 'readwrite'); const gameStore = tx.objectStore('gamesList'); const gameRequest = gameStore.get(thisGameID); gameRequest.onsuccess = () => { // 如果是修正前存的数组数据,要先取数组第一个元素:const game = gameRequest.result[0]; const game = gameRequest.result; game.gameID = thisGameID; const updateGameRequest = gameStore.put(game); updateGameRequest.onsuccess = () => { console.log("Successfully updated this game ID."); } updateGameRequest.onerror = () => reject(updateGameRequest.error); } gameRequest.onerror = () => reject(gameRequest.error); tx.oncomplete = () => resolve(true); tx.onerror = () => reject(tx.error); }); }
内容的提问来源于stack exchange,提问作者SenorMogul
相关产品推荐
相关产品推荐

