我怎么知道IDBCursor达到了它的最后价值?

时间:2017-12-22 00:16:00

标签: javascript asynchronous promise indexeddb

我试图宣传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调用它。但是没有发生这样的事情。

如何正确地做到这一点?

2 个答案:

答案 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事件回调。此外,交易   当标志被清除并且存在时,自动尝试提交   没有待处理的请求这意味着即使是前一个   限制被取消,交易将在任何之前提交   承诺回调被解雇。如果是活跃的旗帜机制   完全删除后,需要引入新的提交模型。

来源:https://github.com/inexorabletash/indexeddb-promises