TransactionInactiveError:无法在“IDBObjectStore”上执行“get”:事务处于非活动状态或已完成

Hen*_*nry 4 safari transactions promise indexeddb

这似乎是 Safari 唯一的错误。据我所知,它不会发生在 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();
Run Code Online (Sandbox Code Playgroud)

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

TransactionInactiveError: Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished
Run Code Online (Sandbox Code Playgroud)

Chrome 中的相同调用没有给出错误,只是返回了预期的 Promise。奇怪的是,在 Safari 中调用 db.test 函数也按预期工作。从字面上看,Safari 中将工作分成两个功能似乎以某种方式导致了此错误。

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

很难在这里追踪问题。

Jos*_*osh 7

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

错误最有可能出现在这些行中:

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

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

var store = transaction.objectStore('unique');
var request = store.get('groceryList');
Run Code Online (Sandbox Code Playgroud)

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

另外一点是您混合使用全局变量。除非您真的是异步代码方面的专家,否则变量 likeparentDbdb只会在某些平台上潜在地导致问题。

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

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

这将让你轻松地将一个开放的 promise 与应该在它之后运行的代码一起编写,如下所示:

connect('groceries').then(function(db) {
  // do stuff with db here
});
Run Code Online (Sandbox Code Playgroud)

接下来,使用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);
     };
   });
}
Run Code Online (Sandbox Code Playgroud)

然后将它们组合在一起

connect().then(function(db) {
  return getGroceryList(db, 'asdf');
}).catch(error);
Run Code Online (Sandbox Code Playgroud)