IndexedDB с двумя транзакциями: 1 чтение, затем 1 обновление - PullRequest
0 голосов
/ 17 января 2019

С IndexedDB я не могу создать вторую транзакцию для моего хранилища объектов (просто список задач), когда я уже создал транзакцию для чтения и отображения данных. Я не получаю сообщение об ошибке в строке, которая пытается создать вторую транзакцию, но ничего не происходит, и код останавливает выполнение.

Эта часть, которая извлекает и отображает мои данные, работает нормально:

var trans;
var ostore;
var db;
var reqdb = window.indexedDB.open("ToDoApp", 2);


reqdb.onupgradeneeded = function (event) {
    console.log("running onupgradeneeded");
    var myDB = event.target.result;
    if (!myDB.objectStoreNames.contains("todos")) {
        myDB.createObjectStore("todos", { keyPath: "ToDoTitle" });
    }
};


reqdb.onsuccess = function (event) {
    db = event.target.result;
    trans = db.transaction(["todos"], "readonly");
    ostore = trans.objectStore("todos");
    var req = ostore.get("TEST IDB 2");   
    $("#btnSave").click(function () {
        UpdateToDo();
    });
    req.onerror = function (event) {
        alert("error");
    };
    req.onsuccess = function (event) {
        $("#ToDoTitle").val(req.result.ToDoTitle);
    };
};

Это получает и отображает вещи просто отлично. Но обратите внимание на функцию UpdateToDo (), которая устанавливается с событием onclick, чтобы я мог на самом деле ОБНОВИТЬ свои данные.

function UpdateToDo(event) {
    alert("1"); 
    var newtransaction = db.transaction(["todos"], "readwrite");
    alert("2");
    var newstore = newtransaction.objectStore("todos");

        newstore.openCursor().onsuccess = function (event) {
            const cursor = event.target.result;
            if (cursor) {
                if (cursor.value.ToDoTitle == 'TEST IDB 2') {       
                    const updateData = cursor.value;
                    updateData.ToDoCategory = 1; // hard coding for now
                    var requpdate = cursor.update(updateData);
                    requpdate.onsuccess = function () {
                        console.log('Updated');
                    };
                    requpdate.onerror = function () {
                        console.log('Error');
                    }
                };
                cursor.continue();
            } else {
                console.log('Cursor error.');
            }
        };
}

Это первое предупреждение срабатывает, а второе - нет. Я предположил, что, поскольку первый обратный вызов, который создал первую транзакцию, был возвращен, эта транзакция была закрыта, но она все еще блокирует мне создание этой транзакции. Если я выполню первую транзакцию ПОЛНОСТЬЮ, вторая транзакция будет создана, и будет запущено второе предупреждение ... тогда я смогу создать курсор и обновить данные.

Я попытался сделать первую транзакцию и хранилище объектов как глобальные переменные, но это тоже не сработало.

Кажется нелепым позволять вам выполнять только одну транзакцию на страницу. Как еще я должен изначально загружать данные, а затем разрешать пользователю обновлять их? Я что-то упустил?

Ответы [ 2 ]

0 голосов
/ 18 января 2019

Всего несколько баллов:

  • Не использовать alert вместе с вызовами indexedDb. indexedDB является асинхронным. alert не асинхронный, он останавливает выполнение во время отображения, что может привести к некоторому странному поведению. Лучше использовать console.log, потому что это не останавливает выполнение.
  • Не смешивать код, который выполняет модификацию DOM, и код, который выполняет запросы к базе данных в одной и той же функции (или вложенной). Я бы организовал код вашего проекта в более мелкие функции, которые просто выполняют одну вещь.
  • Не используется глобальная переменная базы данных. Вместо этого каждый раз открывайте базу данных. Открытие базы данных один раз, а затем создание одной или нескольких транзакций по клику может сработать, но я бы посоветовал вместо этого создавать подключение к базе данных каждый раз, когда это необходимо.
  • Рассмотрите возможность использования одной транзакции. Вы можете отправлять запросы на чтение и запись для одной и той же транзакции.
  • Подумайте об использовании async / await вместе с обещаниями, если ваша среда развертывания поддерживает это. Если все сделано правильно, ваш код станет очень читабельным.
  • Если вы просто обновляете одно значение, используйте IDBObjectStore.prototype.get вместо openCursor
  • При выполнении запроса на запись в readwrite txn большую часть времени прослушивание успешного запроса указывает на успех, но не всегда, поэтому лучше прослушивать событие завершения txn.

Например, вот некоторый псевдокод, следующий за некоторыми из приведенных выше предложений:

function connect(name, version, upgrade) {
  return new Promise((resolve, reject) => {
    var req = indexedDB.open(name, version);
    req.onupgradeneeded = upgrade;
    req.onsuccess = event => resolve(req.result);
    req.onerror = event => reject(req.error);
  });
}

function onupgradeneeded(event) {
  var db = event.target.result;
  db.createObjectStore(...);
}

function updateTodo(db, todo) {
  return new Promise((resolve, reject) => {
    var tx = db.transaction('todos', 'readwrite');
    tx.onerror = event => reject(event.target.error);
    tx.oncomplete = resolve;
    var store = tx.objectStore('todos');
    var req = store.get(todo.title);
    req.onsuccess = event => {
      // When using get instead of openCursor, we just grab the result
      var old = event.target.result;

      // If we are updating we expect the current db value to exist
      // Do not confuse a get request succeeding with it actually matching 
      // something. onsuccess just means completed without error. 
      if(!old) {
        const error = new Error('Cannot find todo to update with title ' +  todo.title);
        reject(error);
        return;
      }

      // Change old todo object props
      old.category = todo.category;
      old.foo = todo.bar;

      // Replace old with a newer version of itself
      store.put(old);
   };
 });
}

function onpageloadInitStuff() {
  mybutton.onclick = handleClick;
}

async function handleClick(event) {
  var db;
  try {
    db = await connect('tododb', 1, upgradeneededfunc);
    var newtodo = todolist.querySelector('todolist[checked]');
    await updateTodo(db, newTodo);
  } catch(error) {
    alert(error); // it is ok to alert here, still better to console.log though
  } finally {
    if(db) {
      db.close();
    }
  }
}
0 голосов
/ 17 января 2019

Искандар и я имели ту же идею одновременно. В функции UpdateToDo я открыл полностью отдельное соединение с экземпляром IndexedDB и подключил мою новую транзакцию / хранилище к этому экземпляру. Работа с этим запросом работала

    function UpdateToDo() {
    var udb = window.indexedDB.open("ToDoApp", 2);
    alert("1"); 
    udb.onsuccess = function (event) {
        alert("2");
        db2 = event.target.result;
        var trans2 = db2.transaction(["todos"], "readwrite");
        var ostore2 = trans2.objectStore("todos");
        alert("3");
        var blabla = ostore2.openCursor();
    blabla.onsuccess = function (event2) {    

Исходя из фона MS Sql Server / ASP.NET, открытие нескольких подключений к одной и той же БД кажется интуитивно понятным.

Кажется, что IndexedDB и Cache API не были действительно стабильными в Safari до прошлого года, а работники сферы обслуживания вообще не были включены в Safari до прошлого года (2018). Хотя у Android есть глобальный рынок, в Северной Америке, если Apple его не поддерживает, разработки не так много ... он как бы объясняет последнюю из документации и руководств по этим новым функциям. Всем удачи

...