IndexDB游标的正确打开方式
引言
我最近在维护我的玩具项目react-door
,当我试图使用IndexDB的游标遍历某个表时,我遇到了一些让人十分困惑的问题,下面我将带大家来复现一下事故的现场。
复现
首先,在我的印象中,游标类似迭代器,所以刚开始我试图拿到游标传递出去然后在dao层调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
const getCursor = () => { if (!db) { return Promise.reject("database connect instance can't be null") } const store = db.transaction(storeName, 'readonly').objectStore(storeName) const request = store.openCursor(); return new Promise((resolve, reject) => { request.onsuccess = (e) => { const cursor = e.target["result"] resolve(cursor) } request.onerror = (e) => { reject(e) } }) }
async function someFunc() { const cursor = await getCursor(); if (cursor) { cursor.continue() } else { console.log("no more data") } }
|
一开始我是这么设想的,但是我发现了一个令人特别疑惑的问题:为什么在MDN的文档中,使用if...else...
来遍历这个cursor呢?
1 2 3 4 5 6 7 8 9 10
| if (cursor) { var listItem = document.createElement("li"); listItem.innerHTML = cursor.value.albumTitle + ", " + cursor.value.year; list.appendChild(listItem); cursor.continue(); } else { console.log("Entries all displayed."); }
|
当时我也是借鉴MDN,使用if...else...
来遍历,但是我发现我的代码只能运行一次,这肯定不叫遍历啊。谁家好人用if...else
做遍历啊??? 年轻人不气盛能叫年轻人吗?这肯定是MDN写错了!根据我的经验,立刻给他改写成了这样:
1 2 3 4 5 6 7 8 9 10 11
| async function someFunc() { const cursor = await getCursor(); while (cursor) { console.log(cursor.key); cursor.continue() } console.log("no more data") }
|
看起来没什问题,非常正确!
但是 ,在我实际运行这段代码的时候,并不能正常运行。我们希望它的输出是1 2 3 4 5 ....
,而实际的输出结果却是1 1 1 1 1 ...
。为啥会出现这种结果呢?😥本来我以为是cursor.continue()
方法是异步的,但是结果这是一个完完全全的同步函数。怎么回事呢?
如果说continue
方法不起作用的话,但是它确实能够帮我们将cursor向前移位,并在cursor结束时结束循环(虽然报了一个info),但是如果说它起作用的,我们的cursor每次都指向第一条数据。这是怎么回事呢???
难道IndexDB这个方法还有bug?不可能吧?难道真的是使用if...else
遍历?于是我再次查阅MDN,现在我为大家贴上MDN完整的代码段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function displayData() { var transaction = db.transaction(["rushAlbumList"], "readonly"); var objectStore = transaction.objectStore("rushAlbumList");
objectStore.openCursor().onsuccess = function (event) { var cursor = event.target.result; if (cursor) { var listItem = document.createElement("li"); listItem.innerHTML = cursor.value.albumTitle + ", " + cursor.value.year; list.appendChild(listItem);
cursor.continue(); } else { console.log("Entries all displayed."); } }; }
|
经过我的观察,难道…cursor只能在openCursor的回调中使用??
带着这个猜想,我将我的代码改为这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| if (!db) { return Promise.reject("database connect instance can't be null") } const store = db.transaction(storeName, 'readonly').objectStore(storeName) const request = store.openCursor(); return new Promise((resolve, reject) => { request.onsuccess = (e) => { const res = [] const cursor = e.target["result"] if (cursor) { res.push(cursor.value) cursor.continue() } else { resolve(res) } } request.onerror = (e) => { reject(e) } })
|
嗯,果然不出所料,现在我们已经可以正确的遍历cursor了!但是又有一个问题随之而来,为什么每次我的res只有一个数据😅?经过我的一番思考,一个很恐怖的想法闪现出来,沃日,indexDB你不会….每次游标移位都要调用onSuccess回调吧?
于是我们再次改写代码,写成这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| if (!db) { return Promise.reject("database connect instance can't be null") } const store = db.transaction(storeName, 'readonly').objectStore(storeName) const request = store.openCursor();
const res = [] return new Promise((resolve, reject) => { request.onsuccess = (e) => { const cursor = e.target["result"] if (cursor) { res.push(cursor.value) cursor.continue() } else { resolve(res) } } request.onerror = (e) => { reject(e) } })
|
这时,我们终于如愿以偿的拿到了我们需要的数据(真是辛苦啊😭)
总结
现在我们来总结一下cursor的坑:
所以上面那一版代码就是使用cursor遍历的标准模板,因为这个过程是类似递归的过程,所以我们的cursor是真的使用if...else...
遍历代码😥,没毛病哦兄弟们