Encapsulate IndexedDB with a Promise

IndexedDB is a large NoSQL storage system. It lets you store almost anything in the user’s browser. In addition to the usual additions and deletions, IndexedDB also supports transactions.

Why use IndexdDB

IndexedDB has the following features

  1. IndexedDB stores key-value pairs. IndexedDB uses an Object Store to store data. All types of data can be stored directly, including JavaScript objects. In the object warehouse, data is stored in the form of “key and value pairs”. Each data record has a corresponding primary key. The primary key is unique and cannot be repeated, otherwise an error will be thrown.
  2. The IndexedDB API is primarily asynchronous. IndexedDB operates without locking the browser and allows users to perform other operations, in contrast to LocalStorage, where operations are synchronized. The asynchronous design is designed to prevent large amounts of data being read and written, slowing down the performance of the web page.
  3. IndexedDB is built on a transactional database model. IndexedDB supports transactions, which means that if one of the steps fails, the entire transaction is cancelled and the database is rolled back to where it was before the transaction occurred. There is no case where only a portion of the data is overwritten.
  4. Same-origin restriction IndexedDB is subject to same-origin restriction, with each database corresponding to the domain name for which it was created. A web page can only access databases within its own domain name, but cannot access databases across domains.
  5. Large storage space The storage space of IndexedDB is much larger than that of LocalStorage. The global limit is 50% of the disk, and the group limit is 20% of the disk. Some browsers limit each write to 130M
  6. Supports binary storage. IndexedDB can store not only strings, but also binary data (ArrayBuffer objects and Blob objects).

Native process implementation

IndexedDB is good, but the usage is not as simple as LocalStorage. Let’s first look at the steps and writing method.

  1. Start by opening the database -> indexeddb.open () -> IDBDatabase
  2. Start a transaction -> idbDatabase.Transaction () -> IDBTransaction ->IDBObjectStore
  3. The new database (IDBObjectStore createObjectStore ())
  4. Add data (idBobjectStore.add ()), read data (idBobjectStore.get ()), update data (idBobjectStore.put ()), delete data (idBobjectStore.delete ())
  5. Traversing the data (IDBObjectStore openCursor ())

1. Open the database

// IDBOpenDBRequest indicates the request to open the database
const request: IDBOpenDBRequest  = indexedDB.open( databaseName, version );
// When the version is updated and a new store is created
request.onupgradeneeded = ( event ) = > {
 // // IDBDatabase Indicates the connection to the database. This is the only way to get a database transaction.
 const db: IDBDatabase = event.target.result;
 if ( db.objectStoreNames.contains( stroeName ) === false ) {
   db.createObjectStore( stroeName, {keyPath: 'key'}); } openSuccess(db); }; request.onsuccess =( event ) = > {
 // IDBDatabase indicates the connection to the database. This is the only way to get a database transaction.
 const db: IDBDatabase = event.target.result;
 openSuccess(db);
};
request.onerror = ( event ) = > {
 console.error( 'IndexedDB', event );
};
Copy the code

2. Start a transaction

function openSuccess (db: IDBDatabase) {
 transaction: IDBTransaction = db.transaction( [ storeName ], 'readwrite' );
}
Copy the code

3. GetIDBObjectStore

function openSuccess (db: IDBDatabase) {
 transaction: IDBTransaction = db.transaction( [ storeName ], 'readwrite' );
 objectStore: IDBObjectStore = transaction.objectStore(storeName);
}
Copy the code

4.Add delete change check to add as an example

const request: IDBRequest = objectStore.add(data);
request.onsuccess = function ( event ) {};
request.onerror = function ( event ) {};
Copy the code

5. Iterate over the data

const request = objectStore.openCursor();
request.onsuccess = ( event ) = > {
 let cursor = event.target.result;
 // No data is traversable. Cursor === null
 if(cursor ! = =null) {
   callback(cursor.value);
   / / the nextcursor.continue(); }}; request.onerror =(event) = > {
 callback(event)
}
Copy the code

Effect of original writing method:

Disadvantages of native writing:

  • The objectStrore path is too long
  • Too many callback functions
  • Expandability of

So let’s try encapsulating the IndexedDB API with a Promise + class

Promise code implementation

The key point is that objectStore acquisition is complicated, through open, transaction. We get it, we cache it, we add, we delete, we check and we get the ObjectStore through a getObjectStore method.

The entire structure is as follows:

class DB {
 constructor(databaseName, version, storeOptions) {}
 /** * Open database */
 open(databaseName, version, storeOptions) {}
 async _getTransaction(storeName, version) {}
 /** * Get store */
 async _getObjectStore(storeName, version) {}
 /**
 * 获取一个store
 */
 collection(storeName, version) {
   this.currentStore = storeName;
   this._getObjectStore(storeName, version);
   return this;
 }
 async get(data) {}
 async add(data) {}
 async delete(data) {}
 async put(data) {}
 async clear(storeName) {}
 async each(callback){}}Copy the code

As mentioned above, the ObjectStore is obtained through transaction, which in turn is obtained through indexeddb.open ()

First, initialize the important parameters

constructor(databaseName, version, storeOptions) {
 // Cache database name: name + version = key
 this._dbs = {};
 this._databaseName = databaseName;
 this.open(databaseName, version, storeOptions);
}
Copy the code

Open needed to listen to onupgradenneeded, which needs to be triggered when version updates occur, which needs to create store and primary key, otherwise transactions will fail to retrieve

open(databaseName, version, storeOptions) {
   return new Promise((resolve, reject) = > {
     // There is a cache
     if (this._dbs[databaseName + version]) {
       resolve(this._dbs[databaseName + version]);
       return;
     }

      const request = indexedDB.open(databaseName, version);
       // When the version is updated and a new store is created
      request.onupgradeneeded = (event) = > {
          // IDBDatabase
         const database = event.target.result;
         // Cache it
         this._dbs[databaseName + version] = database;
         for (const key in storeOptions) {
           if (database.objectStoreNames.contains(key) === false) {
             const keyPath = storeOptions[key] ? storeOptions[key] : [];
             database.createObjectStore(key, { keyPath });
            }
         }
         resolve(database);
      };
     request.onsuccess = (event) = > {
       // IDBDatabase
       const database = event.target.result;
       // Cache it
       this._dbs[databaseName + version] = database;
       resolve(database);
     };
     request.onerror = (event) = > {
       reject(event);
       console.error('IndexedDB', event);
     };
   });
}
Copy the code

The next step is to get the transaction and get the ObjectStore object through the transaction

async _getTransaction(storeName, version) {
 let db;
 // Get it from cache first
 if (this._dbs[this._databaseName + version]) {
   db = this._dbs[this._databaseName + version];
 } else {
   db = this.open(this._databaseName, version);
 }
 return db.transaction( [ storeName ], 'readwrite' );
}

/** * get store * ObjectStore: specifies the ObjectStore that allows access to a set of data in the IndexedDB database, */
async _getObjectStore(storeName, version) {
 let transaction = await this._getTransaction(storeName, version);
 return transaction.objectStore( storeName );
}
Copy the code

ObjectStore after you get the ObjectStore, you can directly add, delete, change and check. These methods are simple, just one layer through the Promise package

get(data) {
   return new Promise((resolve, reject) = > {
     this._getObjectStore(this.currentStore).then((objectStore) = > {
       const request = objectStore.get(data);
       request.onsuccess = function ( event ) {
         resolve(event.target.result);
       };
       request.onerror = (event) = >{ reject(event); }}); }); }add(data) {
 return new Promise((resolve, reject) = > {
     this._getObjectStore(this.currentStore).then((objectStore) = > {
       const request = objectStore.add(data);
       request.onsuccess = function ( event ) {
         resolve(event.target.result);
       };
       request.onerror = (event) = >{ reject(event); }}); }); }delete(data) {
 return new Promise((resolve, reject) = > {
     this._getObjectStore(this.currentStore).then((objectStore) = > {
       const request = objectStore.delete(data);
       request.onsuccess = function ( event ) {
         resolve(event.target.result);
       };
       request.onerror = (event) = >{ reject(event); }}); }); }put(data) {
 return new Promise((resolve, reject) = > {
     this._getObjectStore(this.currentStore).then((objectStore) = > {
       const request = objectStore.put(data);
       request.onsuccess = function ( event ) {
         resolve(event.target.result);
       };
       request.onerror = (event) = >{ reject(event); }}); }); }clear(storeName) {
 return new Promise((resolve, reject) = > {
     this._getObjectStore(this.currentStore).then((objectStore) = > {
       const request = objectStore.clear(data);
       request.onsuccess = function ( event ) {
         resolve(event.target.result);
       };
       request.onerror = (event) = >{ reject(event); }}); }); }Copy the code

Results show

It ends up being a lot simpler to use

  • Simplify the steps of creating a DB
  • Cache the objectStore
  • API promiser, use more concise

import DB from './BD';

Open the database and start a transaction */
// open -> transaction -> objectStore
const db = new DB('db', { store: 'id' });

const store = db.collection('store');

const data = [
    {
 name: '甲'.id: 100}, {name: '乙'.id: 1001,},];/* 增 */
store.add(data[1]).then(ev= > {  
 store.add(data[0])
 store.add(data[1])});/ * * / delete
store.delete(data[0].id).then(ev= > {
 console.log(ev);
});

/* 改 */
store.put(data[0]).then((ev) = > {
 // store.put(data[0]).then(ev => {
 // });
 store.get(data[0].id).then((result) = > {
   console.log(result);
 });
});

/* 查 */
store.get(data[0].id).then((result) = > {
 console.log(result);
});

/ * traversal * /
store.each((result) = > {
 console.log(result);
});

Copy the code

conclusion

If you encounter the need for frequent storage operations on the front end or large file caches, consider using IndexedDB. Of course, in the project, it is recommended to directly use the third-party library Zangodb and dexie.js API, which is richer.