onblocked срабатывает при попытке открыть БД с более высоким номером версии

#javascript #html #indexeddb

#javascript #HTML #indexeddb

Вопрос:

Я создаю базу данных IndexedDB products_db .

  • onload добавление ObjectStore products_os в базу данных.
  • получение данных с сервера и добавление данных в products_os .
  • отображение данных в пользовательском интерфейсе.
  • onclick кнопки добавить, пытаясь открыть базу данных с более высоким номером версии и создавая новый ObjectStore cart_os . Здесь это работает не так, как ожидалось.

Текущее поведение:

  • Когда я открываю браузер Chrome в режиме инкогнито и загружаю свойindex.html файл на вкладке, в пользовательском интерфейсе отображается одна строка таблицы, но на Application вкладке Developer Tools product_db базы данных в indexedDB нет.
  • При нажатии кнопки добавить появляется предупреждение о onblocked событии, как указано в function addCartData(e) { . (На этом сайте нет другой открытой вкладки!). После закрытия всплывающего окна теперь нажатие на ту же кнопку добавить ничего не делает.
  • Теперь, если я обновлю страницу с помощью cmd shift r, к таблице будет добавлена новая строка. Теперь в ней всего две строки. При каждом обновлении добавляется новая строка. Предыдущий пункт верен для всех кнопок добавления, которые находятся в пользовательском интерфейсе, нажатие на любую из них вызовет предупреждение, а затем все они станут бесполезными.(нет всплывающего onclick).
  • Если я очищу хранилище, а затем выполню cmd shift r, я снова получу только одну строку в пользовательском интерфейсе.

Ожидаемое поведение:

  • IndexedDB на вкладке Приложения Инструментов разработчика не должен быть пустым. Вместо этого он должен быть заполнен db и objectStore .
  • При обновлении, без очистки хранилища, каждый раз должна отображаться только одна строка.
  • onclick при нажатии кнопки добавить база данных должна открыться с новым номером версии, и к ней должен быть добавлен ObjectStore cart_os . Чтобы позже я мог использовать cart_os для read/write транзакции.

index.html

 <!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Product Table</title>
    <link href="styles.css" rel="stylesheet">
    <script src="script.js" defer></script>
  </head>
  <body>
    <table id='idb-table' style="width=100%" />
  </body>
</html>
  

script.js

 // Create an instance of a db object for us to store the open database in
let db;

window.onload = function() {
  // Open our database; it is created if it doesn't already exist
  let request = window.indexedDB.open('products_db', 1);

  // onerror handler signifies that the database didn't open successfully
  request.onerror = function() {
    console.log('Database failed to open');
  }

  // onsuccess handler signifies that the database opened successfully
  request.onsuccess = function() {
    console.log('Database opened successfully');

    // Store the opened database object in the db variable. This is used a lot below
    db = request.result;

    // Run the fetchProducts() function to fetch data from external API 
    fetchProducts();
  }

  // Setup the database tables if this has not already been done
  request.onupgradeneeded = function(e) {
    // Grab a reference to the opened database
    let db = e.target.result;

    // Create an objectStore to store our products in (basically like a single table)
    // including a auto-incrementing key
    let objectStore = db.createObjectStore('products_os', {keyPath: 'id', autoIncrement: true});

    // Define what data items the objectStore will contain
    objectStore.createIndex('title', 'title', {unique: false});
    objectStore.createIndex('price', 'price', {unique: false});
    objectStore.createIndex('inStock', 'inStock', {unique: false});

  }

  function fetchProducts() {
    let product1 = fetch('http://localhost:3001/location/pathname') // see product.json file
      .then(function(response) {
        return response.json();
      })
      .then(function(response) {
        addData(response);
      })
  }

  function addData(value) {
    // open a read/write db transaction, ready for adding the data
    let transaction = db.transaction(['products_os'], 'readwrite');

    // call an object store that's already been added to the database
    let objectStore = transaction.objectStore('products_os');

    let newItem = {
      title: value.title,
      price: value.sellingPrice,
      inStock: value.inStock,
    };

    // Make a request to add our newItem object to the object store
    var request = objectStore.add(newItem);

    transaction.oncomplete = function() {
      console.log('Transaction completed: database modification finished.');

      // update the display of data to show the newly added item, by running displayData() again.
      displayData();
    }

    transaction.onerror = function() {
      console.log('Transaction not opened due to error');
    };
  }

  function displayData() {
    // Open our object store and then get a cursor - which iterates through all the
    // different data items in the store
    let objectStore = db.transaction('products_os').objectStore('products_os');

    objectStore.openCursor().onsuccess = function(e) {
      // Get a reference to the cursor
      let cursor = e.target.result;

      // Get a reference to table
      let table = document.querySelector('#idb-table');

      // If there is still another data item to iterate through, keep running this code
      if(cursor) {
        // Insert into table,
        // structure the HTML fragment, and append it inside the table
        let tableRow = document.createElement('tr');
        for (let val in cursor.value) {
          if (Object.prototype.hasOwnProperty.call(cursor.value, val)) {
            if (val !== 'id') {
              let td = document.createElement('td');
              tableRow.appendChild(td);
              td.textContent = cursor.value[val];
            }
          }
        }
        let td = document.createElement('td');
        td.setAttribute('align', 'center');
        let addButton = document.createElement('button');
        td.appendChild(addButton);
        addButton.textContent = 'Add';
        addButton.addEventListener('click', function(e) {
          addCartData(e);
        })
        tableRow.appendChild(td);
        table.appendChild(tableRow);

        // Iterate to the next item in the cursor
        cursor.continue();
      }
    }
  }

  function addCartData(e) {
    // Open the db with higher version number
    let req = window.indexedDB.open('products_db', 2);

    req.onerror = function() {
      console.log('Database failed to open');
    }

    req.onsuccess = function() {
      console.log('Database opened successfully');

      // Store the opened database object in the db variable. This is used a lot below
      db = req.result;
    }

    req.onblocked = function(event) {
      // If some other tab is loaded with the database, then it needs to be closed
      // before we can proceed.
      alert("Please close all other tabs with this site open!");
    };

    req.onupgradeneeded = function(e){
      // Grab a reference to the opened database
      let db = e.target.result;

      // Create a cart objectStore to store our information added to the cart
      let cartStore = db.createObjectStore('cart_os', {keyPath: 'id', autoIncrement: true});

      // Define what data items the cart objectStore will contain
      cartStore.createIndex('title', 'title', {unique: false});
      cartStore.createIndex('price', 'price', {unique: false});
    }
  }
  

styles.css

 table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
} 

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}   

tr:nth-child(even) {
  background-color: #dddddd;
} 

tr:last-child {
  text-align: center!important;
}
  

product.json

 {
   "title": "Newhide Designer",
   "productDescription": "",
   "sellingPrice": 119,
   "inStock": true,
}
  

Редактировать

ObjectStore products_os никогда не добавлялся в БД

Я получаю ошибку Uncaught (in promise) DOMException: Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found. в строке 207, как показано на рисунке.

onupgradeneeded_version1:

 function onupgradeneeded_version1(e) {
  // Grab a reference to the opened database
  let db = e.target.resu<

  // Create an objectStore to store our products in (basically like a single table)
  // including a auto-incrementing key
  let objectStore = db.createObjectStore('products_os', {keyPath: 'id', autoIncrement: true});

  // Define what data items the objectStore will contain
  objectStore.createIndex('title', 'title', {unique: false});
  objectStore.createIndex('price', 'price', {unique: false});
  objectStore.createIndex('inStock', 'inStock', {unique: false});
}
  

Ответ №1:

Что ж, у меня есть масса предложений по улучшению этого кода, но, чтобы перейти прямо к делу, я думаю, проблема связана с тем, что вы сначала открываете соединение с базой данных и оставляете его открытым (на неопределенный срок), а затем в вашем обработчике щелчков открываете вторую версию базы данных с более высоким номером версии, щелчок по которой происходит, когда исходное соединение с базой данных все еще открыто.

Я предлагаю инкапсулировать indexedDB.open => db логику в обещание. Это облегчит чтение и запись кода. Тогда я предлагаю изменить способ использования этого обещания.

Вот о чем я думаю:

 function open(name, version, onupgradeneeded) {
  return new Promise(function executor(resolve, reject) {
    const request = indexeDB.open(name, version);
    request.onsuccess = function(event) { resolve(request.result); };
    request.onerror = function(event) { reject(request.result); };
    request.onblocked = function(event) { console.debug('blocked indefinitely'); };
    request.onupgradeneeded = onupgradeneeded;
  });
}

async function fetch_product() {
  const response = await fetch('http://localhost:3001/location/pathname');
  const data = await response.json();
  return data;
}

function add_product(db, data) {
  return new Promise(function(resolve, reject) {
    const transaction = db.transaction(..., 'readwrite');
    transaction.oncomplete = resolve;
    transaction.onerror = reject;
    const store = transaction.objectStore(...);
    store.put(data);
  });
}

function get_products(db) {
  return new Promise(function(resolve, reject) {
    const transaction = db.transaction(...);
    const store = transaction.objectStore(...);
    const request = store.getAll();
    request.onsuccess = function(event) {
      const products_array = request.result;
      resolve(products_array);
    };
    request.onerror = function(event) {
      reject(request.error);
    };
  });
}

function render_product(product) {
  const table = document.getElementById('table-id');

  // create rows and all that.
  // and append
  button.onclick = handle_click;
}

async function handle_click(event) {

  // Based on what was clicked, find and build the product data object
  // event.target is the button
  const row = event.target.closest('tr');
  const name = row.querySelector('td.namecolumn').textContent;
  // etc
  const product = {
    name: name
  };

  const db = await open(name, version2, onupgradeneeded_for_version2);
  await add_product(db, product);
  db.close();
}

async function onload() {
  const product = await fetch_product();
  const db = await open(name, version, onupgradeneeded_version1);
  await add_product(db, product);

  const products = await get_products(db);

  // we are done with the db now
  db.close();

  // print out product data
  for(const product of products) {
    render_product(product);
  }  
}
  

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

1. Я написал функцию для onupgradeneeded_version1 , которая точно такая же, как RHS of request.onupgradeneeded = function(e) { из вопроса. В script.js из вопросов, он запускался и, таким образом, создавал ObjectStore products_os в БД. Но если расширить ту же логику с решением, получится Uncaught (in promise) DOMException: Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found. . Пожалуйста, смотрите ПРАВКУ , о которой идет речь.

2. Почему вы это сделали async function onload { ? Почему async там?

3. Указание асинхронности в определении функции позволяет использовать await в ее теле.