IndexedDB:使用promises升级?

时间:2014-08-28 19:36:13

标签: javascript promise indexeddb

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

  • 使用者调用某些函数来打开并升级返回promise的数据库
  • 该功能按顺序执行所有必需的升级/迁移。如果没有发生错误,则通过与数据库的连接来解决承诺
  • 如果在任何阶段发生任何错误,保证承诺将被拒绝且错误
  • 添加新的迁移/升级步骤就像定义执行升级的功能一样简单,所有其他并发问题都由'框架来处理。

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

  • 如果数据库不需要升级,则不会调用onupgraderequired回调(因此,如果数据库不需要升级,那么在升级完成时得到解决的承诺将永远无法解决,并且调用代码并不知道在连接回调时是否会出现这种情况。)
  • 如果一次升级依赖于另一次升级(例如填充您刚创建的商店),则必须等到其onsuccess回调被调用 - 因此每次升级都需要顺序链接
  • 似乎在链中的前任结算后执行的承诺延迟足以标记交易'在它再次需要之前处于非活动状态(我认为他们已经安排了' nextTick',这可能与停止交易的机制相同)。
  • 更新如果一次升级依赖另一次升级,则在调用第一次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; });

3 个答案:

答案 0 :(得分:1)

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);
     });
   });
 }

});

答案 1 :(得分:0)

我终于找到了一种方法来避免这个API的所有肮脏,并找到了一个解决方案,它公开了一个干净的基于promises的接口,并推广到任意数量的数据库迁移。关键问题:

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

我提出的谈判所有这些陷阱的代码如下。

versionchange

答案 2 :(得分:0)

Closure库有promise wrapper for IndexedDB。它的开放数据库方法return a promise和IDB中的所有内容都包含在那里。

所以它与你的想法相似。

以下是示例摘录:

  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包装器,再次使用了闭包库。

使用promise包装所有内容的问题不容易使用,您需要跟踪活动事务。如您所见,您将对数据库查询有三个承诺级别:1)db opening promise 2)transaction promise 3)request promise。

你可以通过在整个应用程序中保持db作为参考来消除第一个承诺,但最后两个将会出现。

所以主要是,我将这三个承诺合并到我的图书馆的一个承诺上。