Операции с данными не откатываются в обещанной транзакции IndexedDB

#javascript #promise #indexeddb

#javascript #обещание #indexeddb

Вопрос:

Когда я вызываю функцию saveDamage() и в Promise.all() в конце функции возникает исключение. Я ожидал, что все предыдущие операции будут отменены, но это не так. Что я делаю не так, пожалуйста? Спасибо!

 saveDamage(damage) {
  return this.transaction('rw', [
    this.DAMAGE_STORE_NAME,
    this.DAMAGE_IMAGE_DATA_STORE_NAME,
    this.DAMAGE_IMAGE_DATA_TEMP_STORE_NAME
  ], (tx) => {
    let deletePromise = (damage.id)
        ? this._deleteDamage(tx, damage.id)
        : Promise.resolve();
    return deletePromise.then(() => {
      // Copy temporary image data of damage into permanent location and update their IDs
      let copyPromises = this._getDamageImageFields(damage).map(field => {
        return this._copyObject(tx, this.DAMAGE_IMAGE_DATA_TEMP_STORE_NAME, this.DAMAGE_IMAGE_DATA_STORE_NAME, field.value.imageDataId)
            .then(id => field.value.imageDataId = id)
      });
      return Promise.all(copyPromises)
          // Save damage
          /* The exception occurs inside _saveObject(). Previous DB operations are not rolled back. */
          .then(() => this._saveObject(tx, this.DAMAGE_STORE_NAME, damage, damage.id)) 
          .then(id => damage.id = id)
          // Delete temporary image data
          .then(() => this._clearStore(tx, this.DAMAGE_IMAGE_DATA_TEMP_STORE_NAME));
    });
  });
}

transaction(mode, storeNames, executorFnc) {
  let tx = this._db.transaction(this._transactionStoreNames(storeNames), this._transactionMode(mode));
  tx.onabort = (event) => reject("Transaction failed: "   event.target.errorCode);
  return executorFnc(tx);
}

_deleteDamage(tx, id) {
  // Fetch damage from DB
  return this._getObject(tx, this.DAMAGE_STORE_NAME, id).then(damage => {
    // Delete all big image data
    let promises = this._getIdsOfDamageImageData(damage)
        .map(imageDataId => this._deleteObject(tx, this.DAMAGE_IMAGE_DATA_STORE_NAME, imageDataId));
    // Delete damage itself
    promises.push(this._deleteObject(tx, this.DAMAGE_STORE_NAME, id));
    return Promise.all(promises);
  });
}

_deleteObject(tx, storeName, id) {
  return new Promise((resolve, reject) => {
    let result = tx.objectStore(storeName).delete(id);
    result.onsuccess = (event) => resolve();
    result.onerror = (event) => reject(event);
  });
}

_copyObject(tx, sourceStoreName, targetStoreName, id) {
  return this._getObject(tx, sourceStoreName, id)
      .then(obj => this._saveObject(tx, targetStoreName, obj));
}

_saveObject(tx, storeName, object, id) {
  return new Promise((resolve, reject) => {
    let store = tx.objectStore(storeName);
    let result = store.put(object, id);
    result.onsuccess = (event) => resolve(event.target.result /* object id */);
    result.onerror = (event) => reject(event);
  });
}
 

Исключение (я знаю, как это исправить):

 Uncaught (in promise) DOMException: Failed to execute 'put' on 'IDBObjectStore': The object store uses in-line keys and the key parameter was provided.
 

РЕДАКТИРОВАТЬ: после некоторых экспериментов я понял, что, как только я выхожу из обработчика успеха и больше нет незавершенных обработчиков успеха, транзакция фиксируется. Похоже, что операции, выполняемые в функции ‘then’, выполняются в другой транзакции, потому что ‘then’ выполняется в результате возврата разрешенного обещания при выходе из обработчика успеха. Единственный способ, который я нашел для выполнения зависимых операций в одной транзакции, — это использование простых обработчиков успеха. Но использование простых обработчиков успеха становится затруднительным, когда операции следующего шага зависят от результата текущей операции. Например, когда мне нужно удалить объект ‘A’, хранящийся в хранилище ‘a’, и в хранилище ‘b’ хранится множество подобъектов ‘B’. В таком случае мне нужно сначала извлечь объект ‘A’, затем удалить вложенные объекты ‘B’ один за другим, что требует динамической генерации кода обработки, удаления ‘A’. Я не могу поверить, что так сложно достичь такой базовой вещи, поэтому я подозреваю, что мне все еще чего-то не хватает.

Ответ №1:

  1. Ошибка означает, что идентификатор не определен, я думаю, в _saveObject
  2. Если ваши обещания представляют отдельные транзакции, то все транзакции в обещании.весь массив, который был зафиксирован до ошибки, будет уже зафиксирован, независимо от того, что обещание Promise.all отклонено. Запустите все операции с базой данных за одну транзакцию, чтобы добиться полного отката в случае сбоя какой-либо одной операции.

Комментарии:

1. Спасибо вам за ваш ответ! Похоже, выполнение всех операций в одной транзакции может быть не так просто. Пожалуйста, проверьте мою правку.