The basic concepts will not be introduced here, please go to MDN or Ruan yifeng’s IndexedDB to learn on your own

The correct use of version

On the open

var request = window.indexedDB.open(name, version);

The first parameter is simply name.

The second argument, version, has its own argument. Version determines the structure of the database, such as what stores there are and what each store looks like.

The version of The database determines The database schema — The object stores in The database and their structure.

  • If the database does not exist, create the database immediately and trigger the onupgradenneeded event
  • If the database exists
    • Version remains unchanged and does not trigger onupgradenrequired event
    • Version has been upgraded, triggering the onupgradenrequired event

Wrong conclusion

From this information, it would be easy for me to come to the wrong conclusion ❎ :

  • Version :1, triggers onupgradeneneeded event in which a store named member is created;
  • Version :2, triggers onupgradeneneeded event in which to create a store called Spend;
  • And so on and so forth.

According to practice, this conclusion can only be successfully implemented under certain conditions, so it is a wrong conclusion, that is to say, it is a wrong approach.

   CreateSpendStore () is never executed when the error case is executed
   var req1 = window.indexedDB.open(name, 1)
   req1.onupgradeneeded = function(){createMemberStore()}
   var req2 = window.indexedDB.open(name, 2)
   req2.onupgradeneeded = function(){createSpendStroe()}
Copy the code

The reason is that the open method does not immediately open the database or start executing a transaction. Instead, it returns an event. If we execute the open method twice in sequence, we actually end up with version: 2 event, so the onupgradenhui hui event is triggered only once, i.e. req1.onupgradenhui hui is executed.

The open request doesn’t open the database or start the transaction right away. The call to the open() function returns an IDBOpenDBRequest object with a result (success) or error value that you handle as an event.

As for success under certain conditions, I mean, if we wait for all reQ1 to be executed, then we can execute REQ2, which is feasible. How do YOU know that REQ1 is fully executed? I don’t know. Let’s force a case

   // Only reQ1 is performed the first time the page is refreshed
   var req1 = window.indexedDB.open(name, 1)
   req1.onupgradeneeded = function(){createMemberStore()}
   // var req2 = window.indexedDB.open(name, 2)
   // req2.onupgradeneeded = function(){createSpendStroe()}
   
   // Only req2 is executed the second time the page is refreshed
   // var req1 = window.indexedDB.open(name, 1)
   // req1.onupgradeneeded = function(){createMemberStore()}
   var req2 = window.indexedDB.open(name, 2)
   req2.onupgradeneeded = function(){createSpendStroe()}
   
   // Follow the above steps to ensure correct execution, but this is too harsh to be desirable.
Copy the code

Correct conclusion

✅ To do this correctly, we should create all stores at once in the onupgradeneneeded event, version upgrade is needed to change and update these stores. Mysql > create table (s);

   // Correct case
   var req1 = window.indexedDB.open(name, 1)
   req1.onupgradeneeded = function(){
     createMemberStore()
     createSpendStore()
   }
Copy the code

Secure upgrade version

Upgrade version several restrictions

  • If a store exists, create it again and throw an error
  • If no store exists, delete store and throw an error
  • If you delete the store, all data will be lost. If you want to save data, you need to read the data required by __ before version update, store it, and write data again after createStore.
    • There is a dark needed pit where data must be read from oldVersion’s DB before or within version Update (cannot be read from the current DB in onupgradeneneeded, 😹😹😹😹 port ()
  • After version upgrade, clear site data to ensure that the new version does not take effect

conclusion

After stomping on the above holes, implement a handy IDB class(using IDB package to solve callback hell) as follows:

// db.js
import { openDB } from 'idb';

export default class IDB {
  constructor({ name, version } = {}) {
    this.db = null;
    this.cacheData = null;
    this.name = name;
    this.version = version;
    this.stores = [];
  }

  // Store it first and then setItems it after it is created
  transfer = async ({ db, storeName } = {}) => {
    // If yes, read the data, cache the memory, and restore the data after the creation

    this.cacheData = await this.getItems({ storeName, db });
    db.deleteObjectStore(storeName);
  }

  // Restore data from transfer
  recovery = async ({ db, storeName } = {}) => {

    await this.setItems({ storeName, values: this.cacheData, db });
  }


  addStore = ({ storeName, keepData, updated, createStore } = {}) = > {
    this.stores.push({ storeName, keepData, updated, createStore });
  }

  // Check whether the store exists
  existStore = ({ db, storeName } = {}) = > {
    db = db || this.db
    if (db) {
      return db.objectStoreNames.contains(storeName)
    }
    return false;
  }

  // Create the table
  createDatabase = async() = > {this.db = await openDB(this.name, this.version, {
      upgrade: async (db, oldVersion, newVersion, transaction) => {
        await Promise.all(this.stores.map(async obj => {
          const { storeName, keepData, updated, createStore } = obj;

          if (this.existStore({ db, storeName })) {
            // If the updated version needs to be updated, do nothing else
            if (updated) {
              if (keepData) { // If the data is saved, the data is transferred, the store is created, and the data is restored
                // We must open oldDB with oldVersion to read the previous data
                const oldDb = await openDB(this.name, oldVersion)
                await this.transfer({ db: oldDb, storeName });
                createStore({ db });
                await this.recovery({ db, storeName });
              } else { // If you do not save the data, delete it directlydb.deleteObjectStore(storeName); createStore({ db }); }}}else {
            createStore({ db })
          }
        }));
      }
    })
  }

  setItems = async ({ storeName, values, db } = {}) => {
    db = db || this.db;

    if (db && this.existStore({ db, storeName })) {

      if (Object.prototype.toString.call(values) === '[object Object]') {
        // Single increment
          db.add(storeName, values)
      } else {
        // Multiple increment
        const tx = db.transaction(storeName, 'readwrite');
        const arr = values.map(item= > {
          return tx.store.add(item);
        })
        arr.push(tx.done)
        await Promise.all(arr)
      }
    }
  }

  editItems = async ({key, values, db, storeName}={})=>{
    db = db || this.db;

    if (db && this.existStore({ db, storeName })) {// Check whether there is a store
      const store = db.transaction(storeName, 'readwrite').objectStore(storeName);
      const _data = await store.get(key)
      Object.keys(values).map(k= >{
        if(_data[k] ! == values[k]){ _data[k] = values[k] } }) store.put(_data) } } getFromIndex =async({store, key, index, fuzzy}={})=>{
    const _index = store.index(index);
    // _index.get(key) By default, only the first element can be retrieved. If there are more than one elements, use cursor
    // return await _index.get(key);

    let cursor = await _index.openCursor();
    const _values = [];
    while (cursor) {
      if (fuzzy) {
        if (cursor.value[index].indexOf(key) > -1) { _values.push(cursor.value); }}else {
        if (cursor.value[index] === key) {
          _values.push(cursor.value);
        }
      }

      cursor = await cursor.continue()
    }
    return _values
  }
  // Return the array structure
  getItems = async ({ storeName, key, db, index, indexName, fuzzy } = {}) => {
    db = db || this.db;

    if (db && this.existStore({ db, storeName })) {// Check whether there is a store
      if (key) {
        const store = db.transaction(storeName).objectStore(storeName);
        if(index){
          let _values = []
          if(Object.prototype.toString.call(index) === '[object Array]') {const __values = await Promise.all(index.map(async item=>{
              return await this.getFromIndex({ store,key, index: item, fuzzy })
            }))
            _values = __values.flat()
          }else{
            _values = await this.getFromIndex({store,key, index, fuzzy})
          }
          return _values;
        }else{
          return [await store.get(key)]
        }
      } else {
        return await db.getAllFromIndex(storeName, indexName)
      }
    }
  }

  delItems = async ({storeName, key, db}={})=>{
    db = db || this.db;

    if (db && this.existStore({ db, storeName })) {// Check whether there is a store
      const store = db.transaction(storeName, 'readwrite').objectStore(storeName);
      await store.delete(key);
    }
  }

  getStoreInstance = ({ storeName, indexName } = {}) = > {
    return {
      setItems: async (values) => {
        return await this.setItems({ storeName, values })
      },
      getItems: async (key, index, fuzzy) => {
        return await this.getItems({ storeName, key, index, indexName, fuzzy })
      },
      editItems: async(key, values)=>{
        return await this.editItems({key, storeName, values})
      },
      delItems: async(key)=>{
        return await this.delItems({key, storeName})
      }
    }
  }
}
Copy the code

usage

// biz.js
import IDB from 'db'

const idb = new IDB({ version: 23.name: 'ziyi' })

idb.addStore({
  storeName: 'member'.keepData: true.updated: false.createStore: ({ db }) = > {

    const store = db.createObjectStore('member', {
      keyPath: 'id'.autoIncrement: true
    });
    store.createIndex('phone'.'phone', { unique: true });
    store.createIndex('name'.'name');
    store.createIndex('score'.'score');
    store.createIndex('hisotry'.'history'); }}); idb.addStore({storeName: 'spend'.updated: false.createStore: ({ db }) = > {
    const store = db.createObjectStore('spend', {
      keyPath: 'id'.autoIncrement: true
    })

    store.createIndex('phone'.'phone');
    store.createIndex('desc'.'desc'); }});const member = idb.getStoreInstance({ storeName: 'member'.indexName: 'phone' })
const spend = idb.getStoreInstance({ storeName: 'spend'.indexName: 'id' })

await idb.createDababase();

await member.getItems();
await member.setItems({phone: xxxx})
awiat member.setItems([{phone: xxxx},{}])
Copy the code