我试图宣传IDBCursor
,就像那样:
/**
* @description Allows asynchronous looping over IDBCursor. The cursor request must be provided and must be new and unused!
*/
class IndexedDBAsyncCursor {
/**
*
* @param {IndexedDBAsyncTable} parentTable
* @param {IDBRequest} cursorRequest
*/
constructor(parentTable, cursorRequest) {
this.cursorRequest = cursorRequest;
this.table = parentTable;
/** @type {Promise<IDBCursor>} **/
this.nextValuePromise = null;
/** @type {IDBCursor} **/
this.lastCursor = null;
this.hasNext = true;
this.hookCursorRequest();
}
/**
* @description Starts waiting for the next value
* @private
*/
makeNextValuePromise() {
if (this.nextValuePromise == null) {
this.rejectPromise = null;
this.resolvePromise =null;
this.nextValuePromise = new Promise((resolve, reject) => {
this.rejectPromise = reject;
this.resolvePromise = resolve;
});
}
}
/**
* Adds event listeners on the cursor
* @private
*/
hookCursorRequest() {
this.makeNextValuePromise();
this.cursorRequest.onsuccess = (event) => {
/** @type {IDBCursor} **/
const cursor = event.target.result;
this.lastCursor = cursor;
if (cursor) {
console.log("[IDB CURSOR] Next value: ", cursor);
this.resolvePromise(cursor);
}
else {
this.hasNext = false;
this.resolvePromise(null);
console.log("[IDB CURSOR] End.");
}
};
this.cursorRequest.onerror = (event) => {
this.hasNext = false;
this.rejectPromise(event);
}
}
/**
* @description Resolves with null or an IDBCursor
* @returns {Promise<IDBCursor>}
*/
async next() {
if (!this.hasNext)
return null;
if (this.lastCursor != null) {
this.makeNextValuePromise();
this.lastCursor.continue();
}
const result = await this.nextValuePromise;
this.nextValuePromise = null;
return result;
}
}
预期用途:
const cursor = new IndexedDBAsyncCursor(this, objectStore.openCursor());
/** @type {IDBCursor} **/
var value = null;
while (value = await cursor.next()) {
if (predicate(value)) {
values.push(value.value);
console.log("[IDB] Found value: ",value.value)
if (oneOnly)
break;
}
else {
console.log("[IDB] Value does not match predicate: ",value.value)
}
}
问题是这段代码:
else {
this.hasNext = false;
this.resolvePromise(null);
console.log("[IDB CURSOR] End.");
}
问题是,一旦达到最后一个值,就不再调用onsuccess
。它根本不再被调用,而我假设最后一次用null
而不是IDBCursor
调用它。但是没有发生这样的事情。
如何正确地做到这一点?
答案 0 :(得分:4)
所介绍的代码适用于Chrome,但不适用于Firefox。仅供参考,我以前用它来驾驶它:
indexedDB.deleteDatabase('so');
const open = indexedDB.open('so');
open.onupgradeneeded = e => {
const db = open.result;
const s = db.createObjectStore('s');
for (let i = 0; i < 4; ++i) {
s.put({name: 's' + i, num: i}, i);
}
};
open.onsuccess = async e => {
const db = open.result;
const tx = db.transaction('s');
const objectStore = tx.objectStore('s')
const values = [], oneOnly = false;
const predicate = x => true;
const cursor = new IndexedDBAsyncCursor(this,objectStore.openCursor());
/** @type {IDBCursor} **/
var value = null;
while (value = await cursor.next()) {
if (predicate(value)) {
values.push(value.value);
console.log("[IDB] Found value: ",value.value)
if (oneOnly)
break;
}
else {
console.log("[IDB] Value does not match predicate: ",value.value)
}
}
};
在Chrome中,记录:
[IDB CURSOR] Next value: IDBCursorWithValue {value: {…}, source: IDBObjectStore, direction: "next", key: 0, primaryKey: 0}
[IDB] Found value: {name: "s0", num: 0}
[IDB CURSOR] Next value: IDBCursorWithValue {value: {…}, source: IDBObjectStore, direction: "next", key: 1, primaryKey: 1}
[IDB] Found value: {name: "s1", num: 1}
[IDB CURSOR] Next value: IDBCursorWithValue {value: {…}, source: IDBObjectStore, direction: "next", key: 2, primaryKey: 2}
[IDB] Found value: {name: "s2", num: 2}
[IDB CURSOR] Next value: IDBCursorWithValue {value: {…}, source: IDBObjectStore, direction: "next", key: 3, primaryKey: 3}
[IDB] Found value: {name: "s3", num: 3}
[IDB CURSOR] End.
问题在于Chrome和Firefox在执行微任务(即&#34;然后&#34; Promises的回调)时不一致。 Chrome是按规格进行的,并且微任务在整个任务中执行。 Firefox尚未修复,微任务稍后执行。更多详情请见https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
正如其他人 - 乔希暗示的那样,这是一个问题,因为在Firefox中,continue()
通话最终会发生在&#34; success
&#34;事件处理程序,并且不允许这样做。您可以通过对async next()
实现进行以下代码更改来查看:
try {
this.lastCursor.continue();
} catch (ex) { console.error(ex.name, ex.message); }
在Firefox中,这会记录:&#34; TransactionInactiveError针对当前未激活或已完成的事务发出请求。&#34; - 因为Firefox在事件的任务之外执行包含下一个调用的微任务,所以事务不再有效。
要在Firefox中修复微任务问题之前完成此工作,您需要重新构建代码,以便直接在&#34; continue()
&#34;中调用success
。处理程序,无论是否使用下一个值,都需要重新处理跟踪/生成promise的方式。
请注意,即使在Firefox中修复了微任务问题,编写的代码仍然很不可靠,因为在while
循环中执行任何其他异步(即引入另一个await
)可能会推动{ {1}}召唤出任务。所以如果你想要,例如在迭代时执行continue()
会破坏。直接在&#34; fetch()
&#34;中执行continue()
如上所述的处理程序将使其更加健壮。
答案 1 :(得分:0)
对这个答案并不完全有信心,但我认为你无法按照游标请求做出承诺。您可以围绕整个光标步进做一个承诺,但不是每次迭代。
原因有点复杂,但它与微任务有关,以及当它没有及时检测到调用cursor.continue
的下一个请求时,交易如何超时。
这是引用:
问题索引的数据库事务与Promises组合不佳。
事务定义为具有在设置时设置的活动标志 创建事务,并在从源发出IDB事件回调时 与该事务相关联的运行。活动标志被清除 当任务完成时,即控制从脚本返回时;对于 例如,在回调结束时。交易中的操作 (put,get等)仅在flag为true时允许。这意味着 你不能在Promise回调中执行操作,因为它 根据定义,它不是IDB事件回调。此外,交易 当标志被清除并且存在时,自动尝试提交 没有待处理的请求这意味着即使是前一个 限制被取消,交易将在任何之前提交 承诺回调被解雇。如果是活跃的旗帜机制 完全删除后,需要引入新的提交模型。