IndexedDB: updating with promises?

Just started my first project with IndexedDb, and I was at a dead end trying to create a system for opening and updating the database on first use. I want to use promises (current angularJs$q , but I am flexible) to give me some guarantees about catching any errors that occur and reduce mental overhead reasoning about failure modes. My requirements:

  • The user calls some functions to open and update the database, which returns a promise.
  • The function performs all updates / migrations required in the sequence. If no errors occur, the promise is resolved when connecting to the database
  • If any error occurs at any stage, the promise will be rejected with an error.
  • Adding a new migration / upgrade step is as simple as defining the function that performs the upgrade; all other concurrency issues will take care of the “structure”.

Problems I have encountered so far:

  • The callback is onupgraderequirednot called if the database does not need to be updated (so the promise that was allowed at the end of the update will never be allowed if the database does not need to be updated, and the calling code does not know if this will happen when connecting callbacks)
  • If one update depends on another (for example, filling up the repository you just created), you should wait until its callback is called onsuccess- therefore, for each update a sequential chain is required
  • , , , , , , ( , "nextTick", , ).
  • , , , onsuccess , versionchange .

, API promises. ( ). ?

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; });
+4
3

- API , promises . :

, , , .

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

Closure IndexedDB. , 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 wrapper, .

, . , : 1) db 2) 3) .

, db , .

Basically, I combine these three promises into one promise in my library.

0
source

All Articles