IndexedDB:是否使用承诺升级?

gr8qqesn  于 2022-12-09  发布在  IndexedDB
关注(0)|答案(3)|浏览(140)

我刚开始我的第一个项目IndexedDb,我在尝试创建一个系统来打开和升级第一次使用的数据库时被难倒了。我想使用promises(目前的angularJs $q服务,但我很灵活)来给予我一些关于捕获任何发生的错误的保证,并减少关于故障模式推理的精神开销。我的要求是:

  • 消费者调用某个函数来打开并升级返回承诺的数据库
  • 该函数按顺序执行所有必要的升级/迁移。如果没有出现错误,则通过连接到数据库解决该问题
  • 如果在任何阶段出现任何错误,承诺保证会因错误而被拒绝
  • 添加一个新的迁移/升级步骤就像定义一个执行升级的函数一样简单,所有其他的并发问题都由“框架”来处理。

到目前为止我遇到的问题:

  • 如果DB不需要升级,则不调用onupgraderequired回调(因此,如果DB不需要升级,则在升级完成时解决的承诺将永远不会得到解决,并且调用代码不知道在连接回调时是否会出现这种情况)
  • 如果一个升级依赖于另一个升级(例如,填充刚创建的存储),则必须等待,直到调用其onsuccess回调-因此每个升级都需要顺序链接
  • 看起来,在链中的前一个承诺解决后,执行承诺的延迟足以在再次需要它之前将“事务”标记为非活动(我认为它们是用“nextTick”调度的,这可能是使事务非活动的同一机制)。
    *update如果一个升级依赖于另一个升级,则在调用第一个升级的onsuccess回调时,versionchange事务不再处于活动状态。

我目前的结论是,这个API从根本上反对基于承诺的方法。我最好的尝试如下(为了便于阅读而简化了一点)。我哪里错了?

var newPromise = function(withDeferred) {
    var deferred = $q.defer();
    try {
        withDeferred(deferred);
    } catch (err) {
        deferred.reject(err); 
    }
    return deferred.promise;
};

var newTransactionPromise = function(getTransaction) {
    return newPromise(function(deferred) {
        var transaction = getTransaction();

        transaction.oncomplete = function(ev) { deferred.resolve(); };
        transaction.onabort = function(ev) { deferred.reject(transaction.error); };
    });
};

var migrations = [
    function(db) {
        return newTransactionPromise(function() {
            // throws: The database is not running a version change transaction.
            return db
                .createObjectStore("entries", { keyPath: 'id', autoIncrement: true })
                .transaction;
        });
    },
    function(db) {
        return newTransactionPromise(function()
        {
            var entryStore = db.transaction("entries", "readwrite").objectStore("entries");
            entryStore.add({ description: "First task" });
            return entryStore.transaction;
        });
    }
];

var upgradeAndOpen = function() {
    return newPromise(function(deferred) {
        var latest_version = migrations.length;
        var request = indexedDB.open("caesium", latest_version);

        request.onupgradeneeded = function(event) {
            try {
                // create an already resolved promise to start a chain
                var setupDeferred = $q.defer(); 
                setupDeferred.resolve();
                var setupComplete = setupDeferred.promise;

                for (var v = event.oldVersion; v < latest_version; v++)
                {
                    // Problem: the versionchange transaction will be 'inactive' before this promise is scheduled
                    var nextMigration = migrations[v].bind(this, request.result);
                    setupComplete = setupComplete.then(nextMigration);
                }

                setupComplete["catch"](deferred.reject);
            } catch (err) {
                deferred.reject(err);
            }
        };

        request.onerror = function(event) { deferred.reject(request.error); };
        request.onsuccess = function(event) { deferred.resolve(request.result); };
    });
};

upgradeAndOpen()["catch"](function(err) { $scope.status = err; });
8yoxcaq7

8yoxcaq71#

var open = function(name, ver) {
  return new Promise(function(yes, no) {
     var req = indexedDB.open(name, var);
     req.onupgradedneeded = function(res) {
       no(req);
       req.onsuccess = null; // for clarity
     };
     req.onsuccess = function() {
       yes(res.result);
     };
     req.onblocked = no;
  }
});

open('db name', 3).then(function(db) {
  // use db here

 }, function(req) {
   // version upgrade logic here

   if (req instanceof IDBResult) {
    return new Promise(function(yes, no) {
      req.transaction.createObjectStore('store_3');
      req.onsuccess = function() {
        yes(req.result);
      });
    });
  }
});
kkih6yb8

kkih6yb82#

我终于找到了一种方法来避免这个API的所有缺点,并且找到了一种解决方案,它公开了一个干净的基于承诺的接口,并推广到任意数量的数据库迁移。

  • 架构更改 * 只能 * 在versionchange事务期间执行;但是数据改变 * 不能 * 在versionchange事务期间执行,因此我们必须区分数据和模式迁移,并且以不同的方式利用不同的事务来执行它们。更新数据改变 * 可以 * 在versionchange事务期间执行,但不是通过通常的db.transaction('readwrite', ...).objectstore(...)方法-这会抛出异常。
  • 为了允许模式创建和填充的任意交错,我们必须将它们视为单独的迁移步骤,并且仅在前一步骤的事务成功后才尝试一个步骤。
  • 规范(https://www.example.com)明确禁止显式事务管理dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#transaction-concept,该规范限制了事务可重用的程度,因为一旦事件循环完成,事务将被标记为非活动状态
  • 因此,方法.open(dbName, version)只允许一个versionchange事务,一旦成功,它就消失了。
  • 因此,多个迁移步骤需要多次连续调用.open(dbName, version)
  • versionchange事务在其他数据库连接打开时阻塞,因此在尝试链中的下一个迁移之前必须关闭每个连接。

下面是我为解决所有这些问题而编写的代码。

var newPromise = function(withDeferred) {
    var deferred = $q.defer();
    try {
        withDeferred(deferred);
    } catch (err) {
        deferred.reject(err); 
    }
    return deferred.promise;
};

var newTransactionPromise = function(getTransaction) {
    return newPromise(function(deferred) {
        var transaction = getTransaction();

        transaction.oncomplete = function(ev) { deferred.resolve(); };
        transaction.onabort = function(ev) { deferred.reject(transaction.error); };
    });
};

var newMigrationPromise = function(dbName, version, migration) {
    return newPromise(function(deferred) {
        var request = indexedDB.open(dbName, version);

        // NB: caller must ensure upgrade callback always called
        request.onupgradeneeded = function(event) {
            var db = request.result;
            newTransactionPromise(
                function() {
                    migration(db, request.transaction);
                    return request.transaction;
                })
                .then(function() { db.close(); })
                .then(deferred.resolve, deferred.reject);
        };

        request.onerror = function(ev) { deferred.reject(request.error); };
    });
};

var migrations = [
    function(db, transaction) {
        db.createObjectStore("entries", { keyPath: 'id', autoIncrement: true });
    },
    function(db, transactionn) {
        db.createObjectStore("entries2", { keyPath: 'id', autoIncrement: true });
    },
    function(db, transaction) {
        var entryStore = transaction.objectStore("entries");

        entryStore.add({description: "First task"});
    }
];

var upgradeAndOpen = function() {
    return open()
        .then(function(db) {
            var version = db.version;
            db.close(); // this connection will block the upgrade (AFAICT)
            return version;
        })
        .then(function(version) {
            return newPromise(function(deferred) {
                // version when created but empty is v1
                // so after the first migration (index: 0) the version must be 2
                var migrationsPerformed = version - 1;
                var migrationCount = migrations.length;

                var previousMigration = newPromise(function(deferred) { deferred.resolve(); });

                for (var index = migrationsPerformed; index < migrationCount; index++)
                {
                    var performNextMigration = newMigrationPromise.bind(this, "caesium", index+2, migrations[index]);
                    previousMigration = previousMigration.then(performNextMigration);
                }

                previousMigration.then(deferred.resolve, deferred.reject);
            });
        })
        .then(open);
};

var open = function() {
    return newPromise(function(deferred) {
        var request = indexedDB.open("caesium");

        request.onsuccess = function(ev) { deferred.resolve(request.result); };
        request.onerror = function(ev) { deferred.reject(request.error); };
    });
};

upgradeAndOpen()
    .then(function() { $scope.status = "completed"; }, function(err) { $scope.status = err; });
7fhtutme

7fhtutme3#

闭包库有promise wrapper for IndexedDB。它的开放数据库方法返回一个promise,IDB中的所有内容都用promise Package 在那里。
所以这和你的想法差不多。
下面是从那里摘录的例子:

goog.db.openDatabase('mydb', 1, function(ev, db, tx) {
    db.createObjectStore('mystore');
  }).addCallback(function(db) {
    var putTx = db.createTransaction(
        [],
        goog.db.Transaction.TransactionMode.READ_WRITE);
    var store = putTx.objectStore('mystore');
    store.put('value', 'key');
    goog.listen(putTx, goog.db.Transaction.EventTypes.COMPLETE, function() {
      var getTx = db.createTransaction([]);
      var request = getTx.objectStore('mystore').get('key');
      request.addCallback(function(result) {
        ...
      });
  });

我已经从那里开始,但产生的API没有工作得很好,所以发生我写了IndexedDB Package 器,再次使用闭包库。
用promise Package 所有东西的问题是不容易使用的,你需要跟踪活动的事务。正如你所看到的,你将有三个级别的数据库查询的承诺,1)数据库打开承诺2)事务承诺3)请求承诺。
您可以通过在global上始终将db作为应用程序的引用来消除第一个承诺,但最后两个承诺仍然存在。
所以主要是,我把这三个承诺合并成一个关于我的图书馆的承诺。

相关问题