IndexedDB:承诺升级吗? [英] IndexedDB: upgrade with promises?

查看:58
本文介绍了IndexedDB:承诺升级吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

只是从IndexedDb开始了我的第一个项目,但是我很想创建一个用于在首次使用时打开和升级数据库的系统.我想使用Promise(当前 angularJs $ q 服务,但我很灵活),以便给我一些有关捕获任何已发生错误的保证,并减少有关故障模式的麻烦.我的要求是:

Just started my first project with IndexedDb, and I'm stumped trying to create a system for opening and upgrading the database on first use. I want to use promises (current the angularJs $q service but I'm flexible) to give me some guarantees about trapping any errors that occur and to reduce the mental overhead reasoning about failure modes. My requirements are:

  • 使用者调用一些函数来打开和升级返回诺言的数据库
  • 该功能按顺序执行所有必需的升级/迁移.如果没有错误发生,则通过连接数据库来解决诺言
  • 如果在任何阶段发生任何错误,则保证会因错误而被拒绝
  • 添加新的迁移/升级步骤就像定义执行升级的功能一样容易,所有其他并发问题都由框架"解决.

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

Problems I've encountered so far:

  • 如果DB不需要升级,则不会调用 onupgraderequired 回调(因此,如果DB不需要升级,则在升级完成时解决的承诺将永远不会解决,并且调用代码不知道在连接回调时是否会发生这种情况)
  • 如果一次升级依赖于另一次升级(例如,填充刚创建的商店),则必须等到其 onsuccess 回调被调用-因此每次升级都需要顺序链接
  • 看来,承诺在其链中的前任解决后执行的延迟足以将交易"标记为无效,然后再次需要(我认为它们与"nextTick"排定了,这可能是停用交易的机制相同.
  • 更新如果一个升级依赖于另一个升级,则在调用第一个的 onsuccess 回调时,不再执行 versionchange 交易活跃.
  • The onupgraderequired callback is not called if the DB doesn't need upgrading (so a promise that got resolved on upgrade complete will never be resolved if the DB doesn't need upgrading, and the calling code doesn't know if this will be the case when wiring up callbacks)
  • If one upgrade relies on another (e.g. populating a store you just created), you must wait until its onsuccess callback is called - so each upgrade needs sequential chaining
  • It appears that the delay for a promise to be executed after its predecessor in the chain resolves is enough to mark the 'transaction' as inactive before it's needed again (I think they're scheduled with 'nextTick', which may be the same mechanism that inactivated the transaction).
  • update if one upgrade relies on another, by the time the onsuccess callback of the first is called, the versionchange transaction is no longer active.

我目前的结论是,该API从根本上敌视基于诺言的方法.下面是我的最佳尝试(为便于阅读,对其进行了简化).我要去哪里错了?

My current conclusion is that the API is fundamentally hostile to a promises-based approach. My best attempt is below (simplified a bit for easier reading). Where am I going wrong?

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

推荐答案

我终于找到了一种避免该API所有麻烦的方法,并且找到了一种解决方案,该解决方案公开了一个基于诺言的干净接口,并且可以推广到任何数量的数据库迁移.关键陷阱:

I finally found a way to avoid all the nastiness of this API and have found a solution that exposes a clean promises-based interface and generalises to any number of database migrations. Key gotchas:

    versionchange 事务期间,只能执行
  • 架构更改.但是在 versionchange 事务期间不能执行 数据更改,因此,我们必须区分数据和架构迁移,并以不同的方式以不同的事务执行它们. 更新数据更改 可以在 versionchange 事务期间执行,但不能通过通常的 db.transaction('readwrite',...).objectstore(...)方法-这会引发异常.而是使用对 versionchange 事务的引用.
  • 要允许模式创建和填充的任意交织,我们必须将它们视为单独的迁移步骤,并且仅在上一步的事务成功完成后才尝试一个步骤.
  • 规范明确禁止显式事务管理(
  • Schema changes can only be performed during a versionchange transaction; but data changes cannot be performed during a versionchange transaction, therefore we must distinguish between data and schema migrations and execute them in different ways, with distinct transactions. update data changes can be performed during a versionchange transaction, but not via the usual db.transaction('readwrite', ...).objectstore(...) method - this throws an exception. Instead use a reference to the versionchange transaction.
  • To allow arbitrary interleaving of schema creation and population, we must treat these as separate migration steps and only attempt one step once the transaction for the previous step has succeeded.
  • Explicit transaction management is explicitly prevented by the spec (https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#transaction-concept ) which limits the extent to which transactions can be re-used as they are marked inactive once the event loop completes
  • Therefore method .open(dbName, version) only allows one versionchange transaction, once it succeeds it's gone. This method is the only way to create versionchange transactions
  • Therefore multiple migration steps require multiple successive calls to .open(dbName, version)
  • versionchange transactions block while other database connections are open, so every connection must be closed before attempting the next migration in the chain.

下面是我用来协商所有这些陷阱的代码.

The code I came up with to negotiate all these gotchas is below.

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

这篇关于IndexedDB:承诺升级吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆