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
- 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.
- 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.
- 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.
- 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.
- 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
- 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.
- Start by opening the database -> indexeddb.open () -> IDBDatabase
- Start a transaction -> idbDatabase.Transaction () -> IDBTransaction ->IDBObjectStore
- The new database (IDBObjectStore createObjectStore ())
- Add data (idBobjectStore.add ()), read data (idBobjectStore.get ()), update data (idBobjectStore.put ()), delete data (idBobjectStore.delete ())
- 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.