TransactionInactiveError:无法执行' get' on' IDBObjectStore':交易无效或已完成

时间:2018-06-14 04:37:00

标签: safari transactions promise indexeddb

这似乎只是Safari的一个bug。据我所知,它不会出现在Chrome中。我有一个非常标准的IndexedDB设置。我调用initDb,保存结果,这为我提供了一个调用数据库的好方法。

var initDb = function() {
    // Setup DB. whenDB is a promise we use before executing any DB requests so we know the DB is fully set up.
    parentDb = null;
    var whenDb = new Promise(function(resolve, reject) {
        var DBOpenRequest = window.indexedDB.open('groceries');
        DBOpenRequest.onsuccess = function(event) {
            parentDb = DBOpenRequest.result;
            resolve();
        };
        DBOpenRequest.onupgradeneeded = function(event) {
            var localDb = event.target.result;
            localDb.createObjectStore('unique', {
                keyPath: 'id'
            });
        };
    });

    // makeRequest needs to return an IndexedDB Request object.
    // This function just wraps that in a promise.
    var request = function(makeRequest, key) {
        return new Promise(function(resolve, reject) {
            var request = makeRequest();
            request.onerror = function() {
                reject('Request error');
            };
            request.onsuccess = function() {
                if (request.result == undefined) {
                    reject(key + ' not found');
                } else {
                    resolve(request.result);
                }
            };
        });
    };

    // Open a very typical transaction
    var transact = function(type, storeName) {
        // Make sure DB is set up, then open transaction
        return whenDb.then(function() {
            var transaction = parentDb.transaction([storeName], type);
            transaction.oncomplete = function(event) {
                console.log('transcomplete')
            };
            transaction.onerror = function(event) {
                console.log('Transaction not opened due to error: ' + transaction.error);
            };
            return transaction.objectStore(storeName);
        });
    };

    // Shortcut function to open transaction and return standard Javascript promise that waits for DB query to finish
    var read = function(storeName, key) {
        return transact('readonly', storeName).then(function(transactionStore) {
            return request(function() {
                return transactionStore.get(key);
            }, key);
        });
    };

    // A test function that combines the previous transaction, request and read functions into one.
    var test = function() {
        return whenDb.then(function() {
            var transaction = parentDb.transaction(['unique'], 'readonly');
            transaction.oncomplete = function(event) {
                console.log('transcomplete')
            };
            transaction.onerror = function(event) {
                console.log('Transaction not opened due to error: ' + transaction.error);
            };
            var store = transaction.objectStore('unique');
            return new Promise(function(resolve, reject) {
                var request = store.get('groceryList');
                request.onerror = function() {
                    console.log(request.error);
                    reject('Request error');
                };
                request.onsuccess = function() {
                    if (request.result == undefined) {
                        reject(key + ' not found');
                    } else {
                        resolve(request.result);
                    }
                };
            });
        });
    };

    // Return an object for db interactions
    return {
        read: read,
        test: test
    };
};
var db = initDb();

当我在Safari中调用db.read('unique', 'test')时,我收到错误:

TransactionInactiveError: Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished

Chrome中的同一个调用不会产生错误,只会返回预期的承诺。奇怪的是,在Safari中调用db.test函数也可以正常工作。从字面上看,似乎是在Safari中将工作分成两个函数会以某种方式导致此错误。

在所有情况下都会记录transcomplete,或者抛出错误(在Safari错误的情况下)或返回正确的值(应该发生)。因此,在错误说事务处于非活动状态或已完成事务之前,事务尚未关闭。

很难在这里追查这个问题。

1 个答案:

答案 0 :(得分:2)

嗯,对我的回答没有信心,但我的第一个猜测是在创建事务和启动请求之间发生的暂停允许事务超时并变为非活动状态,因为它找不到请求处于活动状态,这样以后的请求就会发生尝试启动是在非活动事务上启动的。这可以通过在javascript事件循环的相同时期(相同的时间戳)中启动请求而不是推迟请求的开始来轻松解决。

错误很可能出现在以下几行中:

var store = transaction.objectStore('unique');
return new Promise(function(resolve, reject) {
   var request = store.get('groceryList');

您需要立即创建请求以避免此错误:

var store = transaction.objectStore('unique');
var request = store.get('groceryList');

解决这个问题的一种方法可能就是以不同方式处理代码。 Promise旨在可组合。使用promises的代码通常希望将控制权返回给调用者,以便调用者可以控制流。您当前编写的某些功能违反了此设计模式。通过简单地使用更合适的设计模式,您可能不会遇到此错误,或者至少您将能够更容易地识别问题。

另外一点是你混合使用全局变量。 parentDbdb之类的变量可能会在某些平台上引发问题,除非您真的是异步代码的专家。

例如,从简单的连接或打开函数开始,该函数解析为打开的IDBDatabase变量。

function connect(name) {
  return new Promise(function(resolve, reject) {
    var openRequest = indexedDB.open(name);
    openRequest.onsuccess = function() {
      var db = openRequest.result;
      resolve(db);
    };
  });
}

这将让您轻松地构建一个开放的承诺以及应该在其后运行的代码,如下所示:

connect('groceries').then(function(db) {
  // do stuff with db here
});

接下来,使用promise来封装操作。这不是每个请求的承诺。传递db变量而不是使用全局变量。

function getGroceryList(db, listId) {
   return new Promise(function(resolve, reject) {
     var txn = db.transaction('unique');
     var store = txn.objectStore('unique');
     var request = store.get(listId);
     request.onsuccess = function() {
        var list = request.result;
        resolve(list);
     };
     request.onerror = function() {
        reject(request.error);
     };
   });
}

然后一起撰写

connect().then(function(db) {
  return getGroceryList(db, 'asdf');
}).catch(error);