使用Promise封装IndexedDB
IndexedDB是大型NoSQL存储系统。它使你几乎可以将任何内容存储在用户的浏览器中。除了通常增删改查外,IndexedDB还支持事务。
为何使用IndexdDB
IndexedDB 具有以下特点
- IndexedDB 数据库存储键值对。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- IndexedDB API主要是异步的。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- IndexedDB 构建在事务数据库模型上。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
- 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,全局限制为硬盘的50%,组的限制为硬盘的20%。某些浏览器会限制每次写入130M
- 支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
原生过程实现
IndexedDB 虽好但是用法没有LocalStorage 简单,我们先来看下步骤与写法。
- 首先打开数据库 -> indexedDB.open() -> IDBDatabase
- 开始一个事务 -> IDBDatabase.transaction() -> IDBTransaction ->IDBObjectStore
- 新建数据库 (IDBObjectStore.createObjectStore())
- 新增数据 (IDBObjectStore.add())、读取数据 (IDBObjectStore.get())、更新数据 (IDBObjectStore.put())、删除数据 (IDBObjectStore.delete())
- 遍历数据 (IDBObjectStore.openCursor())
1.打开数据库
// IDBOpenDBRequest 表示打开数据库的请求
const request: IDBOpenDBRequest = indexedDB.open( databaseName, version );
// 版本更新,创建新的store的时候
request.onupgradeneeded = ( event ) => {
// // IDBDatabase 表示与数据库的连接。这是获取数据库事务的唯一方法。
const db: IDBDatabase = event.target.result;
if ( db.objectStoreNames.contains( stroeName ) === false ) {
db.createObjectStore( stroeName, {keyPath: 'key'} );
}
openSuccess(db);
};
request.onsuccess = ( event ) => {
// IDBDatabase 表示与数据库的连接。这是获取数据库事务的唯一方法。
const db: IDBDatabase = event.target.result;
openSuccess(db);
};
request.onerror = ( event ) => {
console.error( 'IndexedDB', event );
};
2.开始一个事务
function openSuccess (db: IDBDatabase) {
transaction: IDBTransaction = db.transaction( [ storeName ], 'readwrite' );
}
3.获取IDBObjectStore
function openSuccess (db: IDBDatabase) {
transaction: IDBTransaction = db.transaction( [ storeName ], 'readwrite' );
objectStore: IDBObjectStore = transaction.objectStore(storeName);
}
4.增删改查以 新增为例
const request: IDBRequest = objectStore.add(data);
request.onsuccess = function ( event ) {};
request.onerror = function ( event ) {};
5.遍历数据
const request = objectStore.openCursor();
request.onsuccess = ( event ) => {
let cursor = event.target.result;
// 没有数据可遍历时 cursor === null
if (cursor !== null) {
callback(cursor.value);
// 下一个
cursor.continue();
}
};
request.onerror = (event) => {
callback(event)
}
原生写法使用效果:
原生写法缺点:
- 获取objectStrore路径过长
- 回调函数过多
- 扩展性差
所以我们来尝试使用Promise + class 封装 IndexedDB API
Promise代码实现
其实关键点是 objectStore获取比较复杂,经过open,transaction。我们获取后将这个对象缓存下来,然后增删改查操作都通过一个getObjectStore方法获取ObjectStore对象。
整个结构如下:
class DB {
constructor(databaseName, version, storeOptions) {}
/**
* 打开数据库
*/
open(databaseName, version, storeOptions) {}
async _getTransaction(storeName, version) {}
/**
* 获取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) {}
}
上面提到获取ObjectStore需要通过transaction获取,而transaction通过indexedDB.open() 获取
首先初始化一下重要参数
constructor(databaseName, version, storeOptions) {
// 缓存数据库名称:name + version为key
this._dbs = {};
this._databaseName = databaseName;
this.open(databaseName, version, storeOptions);
}
open需要监听onupgradeneeded ,版本更新时会进入触发此事件,这种情况要创建好store和主键,不然事务那边会获取失败
open(databaseName, version, storeOptions) {
return new Promise((resolve, reject) => {
// 有缓存的情况
if (this._dbs[databaseName + version]) {
resolve(this._dbs[databaseName + version]);
return;
}
const request = indexedDB.open(databaseName, version);
// 版本更新,创建新的store的时候
request.onupgradeneeded = (event) => {
// IDBDatabase
const database = event.target.result;
// 缓存起来
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;
// 缓存起来
this._dbs[databaseName + version] = database;
resolve(database);
};
request.onerror = (event) => {
reject(event);
console.error('IndexedDB', event);
};
});
}
接下来是获取事务并通过事务得到ObjectStore对象
async _getTransaction(storeName, version) {
let db;
// 先从缓存获取
if (this._dbs[this._databaseName + version]) {
db = this._dbs[this._databaseName + version];
} else {
db = this.open(this._databaseName, version);
}
return db.transaction( [ storeName ], 'readwrite' );
}
/**
* 获取store
* ObjectStore: 表示允许访问IndexedDB数据库中的一组数据的对象存储,
*/
async _getObjectStore(storeName, version) {
let transaction = await this._getTransaction(storeName, version);
return transaction.objectStore( storeName );
}
拿到ObjectStore后可以直接操作增删改查了,这几个方法比较简单,通过Promise包一层就行
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);
}
});
});
}
成果展示
最终使用起来就简洁了很多
- 简化创建一个DB的步骤
- 缓存了objectStore
- API Promise化,使用更简洁
import DB from './BD';
/* 打开数据库并开始一个事务 */
// 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])
});
/* 删 */
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);
});
/* 遍历 */
store.each((result) => {
console.log(result);
});
总结
如果碰到前端频繁存储操作或者大文件缓存的需求,可以考虑使用IndexedDB。当然项目中推荐直接使用第三方库zangodb、dexie.js API更丰富。